diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index 80193d4..9fa662f 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -87,6 +87,7 @@ public class LightlessConfig : ILightlessConfiguration 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 bool EnableNotificationHistory { get; set; } = true; public int NotificationHistorySize { get; set; } = 50; @@ -95,6 +96,11 @@ public class LightlessConfig : ILightlessConfiguration public uint CustomWarningSoundId { get; set; } = 15; // Se15 public uint CustomErrorSoundId { get; set; } = 16; // Se16 public bool UseCustomSounds { get; set; } = false; + public uint PairRequestSoundId { get; set; } = 5; // Se5 + 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 UseFocusTarget { get; set; } = false; public bool overrideFriendColor { get; set; } = false; diff --git a/LightlessSync/Services/NotificationService.cs b/LightlessSync/Services/NotificationService.cs index ea5151e..91f2cbc 100644 --- a/LightlessSync/Services/NotificationService.cs +++ b/LightlessSync/Services/NotificationService.cs @@ -75,7 +75,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ } } - if (notification.SoundEffectId.HasValue && _configService.Current.EnableNotificationSounds) + if (notification.SoundEffectId.HasValue) { PlayNotificationSound(notification.SoundEffectId.Value); } @@ -90,7 +90,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ Message = $"{senderName} wants to pair with you.", Type = NotificationType.PairRequest, Duration = TimeSpan.FromSeconds(180), - SoundEffectId = NotificationSounds.PairRequest, + SoundEffectId = !_configService.Current.DisablePairRequestSound ? _configService.Current.PairRequestSoundId : null, Actions = new List { new() @@ -286,39 +286,37 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ private uint? GetSoundEffectId(NotificationType type, uint? overrideSoundId) { - if (!_configService.Current.EnableNotificationSounds) - return null; - if (overrideSoundId.HasValue) return overrideSoundId; - - if (_configService.Current.UseCustomSounds) - { - return type switch - { - NotificationType.Info => _configService.Current.CustomInfoSoundId, - NotificationType.Warning => _configService.Current.CustomWarningSoundId, - NotificationType.Error => _configService.Current.CustomErrorSoundId, - _ => NotificationSounds.GetDefaultSound(type) - }; - } - return NotificationSounds.GetDefaultSound(type); + // Check if this specific notification type is disabled + bool isDisabled = type switch + { + NotificationType.Info => _configService.Current.DisableInfoSound, + NotificationType.Warning => _configService.Current.DisableWarningSound, + NotificationType.Error => _configService.Current.DisableErrorSound, + _ => false + }; + + if (isDisabled) + return null; + + // Return the configured sound for this type + return type switch + { + NotificationType.Info => _configService.Current.CustomInfoSoundId, + NotificationType.Warning => _configService.Current.CustomWarningSoundId, + NotificationType.Error => _configService.Current.CustomErrorSoundId, + _ => NotificationSounds.GetDefaultSound(type) + }; } private void PlayNotificationSound(uint soundEffectId) { try { - try - { - UIGlobals.PlayChatSoundEffect(soundEffectId); - _logger.LogDebug("Played notification sound effect {SoundId} via ChatGui", soundEffectId); - } - catch (Exception chatEx) - { - _logger.LogWarning(chatEx, "Failed to play sound via ChatGui for ID {SoundId}", soundEffectId); - } + UIGlobals.PlayChatSoundEffect(soundEffectId); + _logger.LogDebug("Played notification sound effect {SoundId} via ChatGui", soundEffectId); } catch (Exception ex) { @@ -390,27 +388,8 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ private void ShowLightlessNotification(NotificationMessage msg) { var duration = msg.TimeShownOnScreen ?? TimeSpan.FromSeconds(_configService.Current.DefaultNotificationDurationSeconds); - uint? soundId = null; - - if (_configService.Current.EnableNotificationSounds) - { - if (_configService.Current.UseCustomSounds) - { - soundId = msg.Type switch - { - NotificationType.Info => _configService.Current.CustomInfoSoundId, - NotificationType.Warning => _configService.Current.CustomWarningSoundId, - NotificationType.Error => _configService.Current.CustomErrorSoundId, - _ => NotificationSounds.GetDefaultSound(msg.Type) - }; - } - else - { - soundId = NotificationSounds.GetDefaultSound(msg.Type); - } - } - - ShowNotification(msg.Title ?? "Lightless Sync", msg.Message ?? string.Empty, msg.Type, duration, null, soundId); + // GetSoundEffectId will handle checking if the sound is disabled + ShowNotification(msg.Title ?? "Lightless Sync", msg.Message ?? string.Empty, msg.Type, duration, null, null); } private void ShowToast(NotificationMessage msg) diff --git a/LightlessSync/UI/LightlessNotificationUI.cs b/LightlessSync/UI/LightlessNotificationUI.cs index 8a34a84..7706142 100644 --- a/LightlessSync/UI/LightlessNotificationUI.cs +++ b/LightlessSync/UI/LightlessNotificationUI.cs @@ -248,6 +248,15 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase { bgColor = bgColor * 1.1f; bgColor.W = Math.Min(bgColor.W, 0.98f); + + // Handle click-to-dismiss for notifications without actions + if (_configService.Current.DismissNotificationOnClick && + !notification.Actions.Any() && + ImGui.IsMouseClicked(ImGuiMouseButton.Left)) + { + notification.IsDismissed = true; + notification.IsAnimatingOut = true; + } } drawList.AddRectFilled( diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 45876ba..1964431 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -44,7 +44,10 @@ public class SettingsUi : WindowMediatorSubscriberBase private readonly ApiController _apiController; private readonly CacheMonitor _cacheMonitor; private readonly LightlessConfigService _configService; - private readonly ConcurrentDictionary> _currentDownloads = new(); + + private readonly ConcurrentDictionary> _currentDownloads = + new(); + private readonly DalamudUtilService _dalamudUtilService; private readonly HttpClient _httpClient; private readonly FileCacheManager _fileCacheManager; @@ -74,17 +77,13 @@ public class SettingsUi : WindowMediatorSubscriberBase private string _lightfinderIconInput = string.Empty; private bool _lightfinderIconInputInitialized = false; private int _lightfinderIconPresetIndex = -1; + private static readonly (string Label, SeIconChar Icon)[] LightfinderIconPresets = new[] { - ("Link Marker", SeIconChar.LinkMarker), - ("Hyadelyn", SeIconChar.Hyadelyn), - ("Gil", SeIconChar.Gil), - ("Quest Sync", SeIconChar.QuestSync), - ("Glamoured", SeIconChar.Glamoured), - ("Glamoured (Dyed)", SeIconChar.GlamouredDyed), - ("Auto-Translate Open", SeIconChar.AutoTranslateOpen), - ("Auto-Translate Close", SeIconChar.AutoTranslateClose), - ("Boxed Star", SeIconChar.BoxedStar), + ("Link Marker", SeIconChar.LinkMarker), ("Hyadelyn", SeIconChar.Hyadelyn), ("Gil", SeIconChar.Gil), + ("Quest Sync", SeIconChar.QuestSync), ("Glamoured", SeIconChar.Glamoured), + ("Glamoured (Dyed)", SeIconChar.GlamouredDyed), ("Auto-Translate Open", SeIconChar.AutoTranslateOpen), + ("Auto-Translate Close", SeIconChar.AutoTranslateClose), ("Boxed Star", SeIconChar.BoxedStar), ("Boxed Plus", SeIconChar.BoxedPlus) }; @@ -107,7 +106,8 @@ public class SettingsUi : WindowMediatorSubscriberBase DalamudUtilService dalamudUtilService, HttpClient httpClient, NameplateService nameplateService, NameplateHandler nameplateHandler, - NotificationService lightlessNotificationService) : base(logger, mediator, "Lightless Sync Settings", performanceCollector) + NotificationService lightlessNotificationService) : base(logger, mediator, "Lightless Sync Settings", + performanceCollector) { _configService = configService; _pairManager = pairManager; @@ -134,8 +134,7 @@ public class SettingsUi : WindowMediatorSubscriberBase SizeConstraints = new WindowSizeConstraints() { - MinimumSize = new Vector2(800, 400), - MaximumSize = new Vector2(800, 2000), + MinimumSize = new Vector2(800, 400), MaximumSize = new Vector2(800, 2000), }; Mediator.Subscribe(this, (_) => Toggle()); @@ -143,7 +142,8 @@ public class SettingsUi : WindowMediatorSubscriberBase Mediator.Subscribe(this, (_) => UiSharedService_GposeStart()); Mediator.Subscribe(this, (_) => UiSharedService_GposeEnd()); Mediator.Subscribe(this, (msg) => LastCreatedCharacterData = msg.CharacterData); - Mediator.Subscribe(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus); + Mediator.Subscribe(this, + (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus); Mediator.Subscribe(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _)); _nameplateService = nameplateService; } @@ -177,6 +177,7 @@ public class SettingsUi : WindowMediatorSubscriberBase DrawSettingsContent(); } + private static bool InputDtrColors(string label, ref DtrEntry.Colors colors) { using var id = ImRaii.PushId(label); @@ -184,12 +185,14 @@ public class SettingsUi : WindowMediatorSubscriberBase var foregroundColor = ConvertColor(colors.Foreground); var glowColor = ConvertColor(colors.Glow); - var ret = ImGui.ColorEdit3("###foreground", ref foregroundColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8); + var ret = ImGui.ColorEdit3("###foreground", ref foregroundColor, + ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Foreground Color - Set to pure black (#000000) to use the default color"); ImGui.SameLine(0.0f, innerSpacing); - ret |= ImGui.ColorEdit3("###glow", ref glowColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8); + ret |= ImGui.ColorEdit3("###glow", ref glowColor, + ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Glow Color - Set to pure black (#000000) to use the default color"); @@ -205,15 +208,17 @@ public class SettingsUi : WindowMediatorSubscriberBase => unchecked(new((byte)color / 255.0f, (byte)(color >> 8) / 255.0f, (byte)(color >> 16) / 255.0f)); static uint ConvertBackColor(Vector3 color) - => byte.CreateSaturating(color.X * 255.0f) | ((uint)byte.CreateSaturating(color.Y * 255.0f) << 8) | ((uint)byte.CreateSaturating(color.Z * 255.0f) << 16); + => byte.CreateSaturating(color.X * 255.0f) | ((uint)byte.CreateSaturating(color.Y * 255.0f) << 8) | + ((uint)byte.CreateSaturating(color.Z * 255.0f) << 16); } private void DrawBlockedTransfers() { _lastTab = "BlockedTransfers"; - UiSharedService.ColorTextWrapped("Files that you attempted to upload or download that were forbidden to be transferred by their creators will appear here. " + - "If you see file paths from your drive here, then those files were not allowed to be uploaded. If you see hashes, those files were not allowed to be downloaded. " + - "Ask your paired friend to send you the mod in question through other means, acquire the mod yourself or pester the mod creator to allow it to be sent over Lightless.", + UiSharedService.ColorTextWrapped( + "Files that you attempted to upload or download that were forbidden to be transferred by their creators will appear here. " + + "If you see file paths from your drive here, then those files were not allowed to be uploaded. If you see hashes, those files were not allowed to be downloaded. " + + "Ask your paired friend to send you the mod in question through other means, acquire the mod yourself or pester the mod creator to allow it to be sent over Lightless.", ImGuiColors.DalamudGrey); if (ImGui.BeginTable("TransfersTable", 2, ImGuiTableFlags.SizingStretchProp)) @@ -235,9 +240,11 @@ public class SettingsUi : WindowMediatorSubscriberBase { ImGui.TextUnformatted(item.Hash); } + ImGui.TableNextColumn(); ImGui.TextUnformatted(item.ForbiddenBy); } + ImGui.EndTable(); } } @@ -265,6 +272,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new DownloadLimitChangedMessage()); } + ImGui.SameLine(); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); _uiShared.DrawCombo("###speed", [DownloadSpeeds.Bps, DownloadSpeeds.KBps, DownloadSpeeds.MBps], @@ -290,6 +298,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new DownloadLimitChangedMessage()); } + _uiShared.DrawHelpText("Controls how many download slots can be active at once."); if (ImGui.SliderInt("Maximum Parallel Uploads", ref maxParallelUploads, 1, 8)) @@ -297,6 +306,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ParallelUploads = maxParallelUploads; _configService.Save(); } + _uiShared.DrawHelpText("Controls how many uploads can run at once."); _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f); @@ -307,7 +317,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new PairProcessingLimitChangedMessage()); } - _uiShared.DrawHelpText("When enabled we stagger pair downloads to avoid large network and game lag caused by attempting to download everyone at once."); + + _uiShared.DrawHelpText( + "When enabled we stagger pair downloads to avoid large network and game lag caused by attempting to download everyone at once."); var limiterDisabledScope = !limitPairApplications; if (limiterDisabledScope) @@ -321,6 +333,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new PairProcessingLimitChangedMessage()); } + _uiShared.DrawHelpText("How many pair downloads/applications can run simultaneously when the limit is on."); if (limiterDisabledScope) @@ -333,7 +346,9 @@ public class SettingsUi : WindowMediatorSubscriberBase { var queueColor = limiterSnapshot.Waiting > 0 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudGrey; var queueText = $"Pair queue {limiterSnapshot.InFlight}/{limiterSnapshot.Limit}"; - queueText += limiterSnapshot.Waiting > 0 ? $" ({limiterSnapshot.Waiting} waiting, {limiterSnapshot.Remaining} free)" : $" ({limiterSnapshot.Remaining} free)"; + queueText += limiterSnapshot.Waiting > 0 + ? $" ({limiterSnapshot.Waiting} waiting, {limiterSnapshot.Remaining} free)" + : $" ({limiterSnapshot.Remaining} free)"; ImGui.TextColored(queueColor, queueText); } else @@ -348,13 +363,16 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.UseAlternativeFileUpload = useAlternativeUpload; _configService.Save(); } - _uiShared.DrawHelpText("This will attempt to upload files in one go instead of a stream. Typically not necessary to enable. Use if you have upload issues."); + + _uiShared.DrawHelpText( + "This will attempt to upload files in one go instead of a stream. Typically not necessary to enable. Use if you have upload issues."); ImGui.Separator(); _uiShared.UnderlinedBigText("Transfer UI", UIColors.Get("LightlessBlue")); ImGuiHelpers.ScaledDummy(5); - _uiShared.DrawHelpText("Download progress notification settings have been moved to the 'Enhanced Notifications' tab for better organization."); + _uiShared.DrawHelpText( + "Download progress notification settings have been moved to the 'Enhanced Notifications' tab for better organization."); ImGuiHelpers.ScaledDummy(5); bool showTransferWindow = _configService.Current.ShowTransferWindow; @@ -363,7 +381,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ShowTransferWindow = showTransferWindow; _configService.Save(); } - _uiShared.DrawHelpText($"The download window will show the current progress of outstanding downloads.{Environment.NewLine}{Environment.NewLine}" + + + _uiShared.DrawHelpText( + $"The download window will show the current progress of outstanding downloads.{Environment.NewLine}{Environment.NewLine}" + $"What do W/Q/P/D stand for?{Environment.NewLine}W = Waiting for Slot (see Maximum Parallel Downloads){Environment.NewLine}" + $"Q = Queued on Server, waiting for queue ready signal{Environment.NewLine}" + $"P = Processing download (aka downloading){Environment.NewLine}" + @@ -375,6 +395,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { _uiShared.EditTrackerPosition = editTransferWindowPosition; } + ImGui.Unindent(); if (!_configService.Current.ShowTransferWindow) ImGui.EndDisabled(); @@ -384,7 +405,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ShowTransferBars = showTransferBars; _configService.Save(); } - _uiShared.DrawHelpText("This will render a progress bar during the download at the feet of the player you are downloading from."); + + _uiShared.DrawHelpText( + "This will render a progress bar during the download at the feet of the player you are downloading from."); if (!showTransferBars) ImGui.BeginDisabled(); ImGui.Indent(); @@ -394,6 +417,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.TransferBarsShowText = transferBarShowText; _configService.Save(); } + _uiShared.DrawHelpText("Shows download text (amount of MiB downloaded) in the transfer bars"); int transferBarWidth = _configService.Current.TransferBarsWidth; if (ImGui.SliderInt("Transfer Bar Width", ref transferBarWidth, 10, 500)) @@ -401,21 +425,27 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.TransferBarsWidth = transferBarWidth; _configService.Save(); } - _uiShared.DrawHelpText("Width of the displayed transfer bars (will never be less wide than the displayed text)"); + + _uiShared.DrawHelpText( + "Width of the displayed transfer bars (will never be less wide than the displayed text)"); int transferBarHeight = _configService.Current.TransferBarsHeight; if (ImGui.SliderInt("Transfer Bar Height", ref transferBarHeight, 2, 50)) { _configService.Current.TransferBarsHeight = transferBarHeight; _configService.Save(); } - _uiShared.DrawHelpText("Height of the displayed transfer bars (will never be less tall than the displayed text)"); + + _uiShared.DrawHelpText( + "Height of the displayed transfer bars (will never be less tall than the displayed text)"); bool showUploading = _configService.Current.ShowUploading; if (ImGui.Checkbox("Show 'Uploading' text below players that are currently uploading", ref showUploading)) { _configService.Current.ShowUploading = showUploading; _configService.Save(); } - _uiShared.DrawHelpText("This will render an 'Uploading' text at the feet of the player that is in progress of uploading data."); + + _uiShared.DrawHelpText( + "This will render an 'Uploading' text at the feet of the player that is in progress of uploading data."); ImGui.Unindent(); if (!showUploading) ImGui.BeginDisabled(); @@ -426,6 +456,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ShowUploadingBigText = showUploadingBigText; _configService.Save(); } + _uiShared.DrawHelpText("This will render an 'Uploading' text in a larger font."); ImGui.Unindent(); @@ -441,30 +472,41 @@ public class SettingsUi : WindowMediatorSubscriberBase using var tree = ImRaii.TreeNode("Speed Test to Servers"); if (tree) { - if (_downloadServersTask == null || ((_downloadServersTask?.IsCompleted ?? false) && (!_downloadServersTask?.IsCompletedSuccessfully ?? false))) + if (_downloadServersTask == null || ((_downloadServersTask?.IsCompleted ?? false) && + (!_downloadServersTask?.IsCompletedSuccessfully ?? false))) { if (_uiShared.IconTextButton(FontAwesomeIcon.GroupArrowsRotate, "Update Download Server List")) { _downloadServersTask = GetDownloadServerList(); } } - if (_downloadServersTask != null && _downloadServersTask.IsCompleted && !_downloadServersTask.IsCompletedSuccessfully) + + if (_downloadServersTask != null && _downloadServersTask.IsCompleted && + !_downloadServersTask.IsCompletedSuccessfully) { - UiSharedService.ColorTextWrapped("Failed to get download servers from service, see /xllog for more information", ImGuiColors.DalamudRed); + UiSharedService.ColorTextWrapped( + "Failed to get download servers from service, see /xllog for more information", + ImGuiColors.DalamudRed); } - if (_downloadServersTask != null && _downloadServersTask.IsCompleted && _downloadServersTask.IsCompletedSuccessfully) + + if (_downloadServersTask != null && _downloadServersTask.IsCompleted && + _downloadServersTask.IsCompletedSuccessfully) { if (_speedTestTask == null || _speedTestTask.IsCompleted) { if (_uiShared.IconTextButton(FontAwesomeIcon.ArrowRight, "Start Speedtest")) { - _speedTestTask = RunSpeedTest(_downloadServersTask.Result!, _speedTestCts?.Token ?? CancellationToken.None); + _speedTestTask = RunSpeedTest(_downloadServersTask.Result!, + _speedTestCts?.Token ?? CancellationToken.None); } } else if (!_speedTestTask.IsCompleted) { - UiSharedService.ColorTextWrapped("Running Speedtest to File Servers...", UIColors.Get("LightlessYellow")); - UiSharedService.ColorTextWrapped("Please be patient, depending on usage and load this can take a while.", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped("Running Speedtest to File Servers...", + UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped( + "Please be patient, depending on usage and load this can take a while.", + UIColors.Get("LightlessYellow")); if (_uiShared.IconTextButton(FontAwesomeIcon.Ban, "Cancel speedtest")) { _speedTestCts?.Cancel(); @@ -472,6 +514,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _speedTestCts = new(); } } + if (_speedTestTask != null && _speedTestTask.IsCompleted) { if (_speedTestTask.Result != null && _speedTestTask.Result.Count != 0) @@ -483,11 +526,13 @@ public class SettingsUi : WindowMediatorSubscriberBase } else { - UiSharedService.ColorTextWrapped("Speedtest completed with no results", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped("Speedtest completed with no results", + UIColors.Get("LightlessYellow")); } } } } + ImGuiHelpers.ScaledDummy(10); } @@ -522,6 +567,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { ImGui.TextUnformatted(transfer.Hash); } + ImGui.TableNextColumn(); ImGui.TextUnformatted(UiSharedService.ByteToString(transfer.Transferred)); ImGui.TableNextColumn(); @@ -530,6 +576,7 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.EndTable(); } + ImGui.Separator(); ImGui.TextUnformatted("Downloads"); if (ImGui.BeginTable("DownloadsTable", 4)) @@ -545,7 +592,8 @@ public class SettingsUi : WindowMediatorSubscriberBase var userName = transfer.Key.Name; foreach (var entry in transfer.Value) { - var color = UiSharedService.UploadColor((entry.Value.TransferredBytes, entry.Value.TotalBytes)); + var color = UiSharedService.UploadColor((entry.Value.TransferredBytes, + entry.Value.TotalBytes)); ImGui.TableNextColumn(); ImGui.TextUnformatted(userName); ImGui.TableNextColumn(); @@ -554,7 +602,8 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.TableNextColumn(); ImGui.TextUnformatted(entry.Value.TransferredFiles + "/" + entry.Value.TotalFiles); ImGui.TableNextColumn(); - ImGui.TextUnformatted(UiSharedService.ByteToString(entry.Value.TransferredBytes) + "/" + UiSharedService.ByteToString(entry.Value.TotalBytes)); + ImGui.TextUnformatted(UiSharedService.ByteToString(entry.Value.TransferredBytes) + "/" + + UiSharedService.ByteToString(entry.Value.TotalBytes)); ImGui.TableNextColumn(); col.Dispose(); ImGui.TableNextRow(); @@ -590,7 +639,9 @@ public class SettingsUi : WindowMediatorSubscriberBase Stopwatch? st = null; try { - result = await _fileTransferOrchestrator.SendRequestAsync(HttpMethod.Get, new Uri(new Uri(server), "speedtest/run"), token, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + result = await _fileTransferOrchestrator.SendRequestAsync(HttpMethod.Get, + new Uri(new Uri(server), "speedtest/run"), token, HttpCompletionOption.ResponseHeadersRead) + .ConfigureAwait(false); result.EnsureSuccessStatusCode(); using CancellationTokenSource speedtestTimeCts = new(); speedtestTimeCts.CancelAfter(TimeSpan.FromSeconds(10)); @@ -613,8 +664,10 @@ public class SettingsUi : WindowMediatorSubscriberBase { _logger.LogWarning("Speedtest to {server} cancelled", server); } + st.Stop(); - _logger.LogInformation("Downloaded {bytes} from {server} in {time}", UiSharedService.ByteToString(readBytes), server, st.Elapsed); + _logger.LogInformation("Downloaded {bytes} from {server} in {time}", + UiSharedService.ByteToString(readBytes), server, st.Elapsed); var bps = (long)((readBytes) / st.Elapsed.TotalSeconds); speedTestResults.Add($"{server}: ~{UiSharedService.ByteToString(bps)}/s"); } @@ -640,6 +693,7 @@ public class SettingsUi : WindowMediatorSubscriberBase st?.Stop(); } } + return speedTestResults; } @@ -647,9 +701,13 @@ public class SettingsUi : WindowMediatorSubscriberBase { try { - var result = await _fileTransferOrchestrator.SendRequestAsync(HttpMethod.Get, new Uri(_fileTransferOrchestrator.FilesCdnUri!, "files/downloadServers"), CancellationToken.None).ConfigureAwait(false); + var result = await _fileTransferOrchestrator.SendRequestAsync(HttpMethod.Get, + new Uri(_fileTransferOrchestrator.FilesCdnUri!, "files/downloadServers"), CancellationToken.None) + .ConfigureAwait(false); result.EnsureSuccessStatusCode(); - return await JsonSerializer.DeserializeAsync>(await result.Content.ReadAsStreamAsync().ConfigureAwait(false)).ConfigureAwait(false); + return await JsonSerializer + .DeserializeAsync>(await result.Content.ReadAsStreamAsync().ConfigureAwait(false)) + .ConfigureAwait(false); } catch (Exception ex) { @@ -667,7 +725,9 @@ public class SettingsUi : WindowMediatorSubscriberBase #if DEBUG if (LastCreatedCharacterData != null && ImGui.TreeNode("Last created character data")) { - foreach (var l in JsonSerializer.Serialize(LastCreatedCharacterData, new JsonSerializerOptions() { WriteIndented = true }).Split('\n')) + foreach (var l in JsonSerializer + .Serialize(LastCreatedCharacterData, new JsonSerializerOptions() { WriteIndented = true }) + .Split('\n')) { ImGui.TextUnformatted($"{l}"); } @@ -679,13 +739,15 @@ public class SettingsUi : WindowMediatorSubscriberBase { if (LastCreatedCharacterData != null) { - ImGui.SetClipboardText(JsonSerializer.Serialize(LastCreatedCharacterData, new JsonSerializerOptions() { WriteIndented = true })); + ImGui.SetClipboardText(JsonSerializer.Serialize(LastCreatedCharacterData, + new JsonSerializerOptions() { WriteIndented = true })); } else { ImGui.SetClipboardText("ERROR: No created character data, cannot copy."); } } + UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server."); _uiShared.DrawCombo("Log Level", Enum.GetValues(), (l) => l.ToString(), (l) => @@ -700,7 +762,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.LogPerformance = logPerformance; _configService.Save(); } - _uiShared.DrawHelpText("Enabling this can incur a (slight) performance impact. Enabling this for extended periods of time is not recommended."); + + _uiShared.DrawHelpText( + "Enabling this can incur a (slight) performance impact. Enabling this for extended periods of time is not recommended."); using (ImRaii.Disabled(!logPerformance)) { @@ -708,6 +772,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { _performanceCollector.PrintPerformanceStats(); } + ImGui.SameLine(); if (_uiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Print Performance Stats (last 60s) to /xllog")) { @@ -721,7 +786,10 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.DebugStopWhining = stopWhining; _configService.Save(); } - _uiShared.DrawHelpText("Having modified game files will still mark your logs with UNSUPPORTED and you will not receive support, message shown or not." + UiSharedService.TooltipSeparator + + _uiShared.DrawHelpText( + "Having modified game files will still mark your logs with UNSUPPORTED and you will not receive support, message shown or not." + + UiSharedService.TooltipSeparator + "Keeping LOD enabled can lead to more crashes. Use at your own risk."); _uiShared.ColoredSeparator(UIColors.Get("LightlessYellow"), 2f); @@ -734,12 +802,14 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared.UnderlinedBigText("Storage", UIColors.Get("LightlessBlue")); ImGuiHelpers.ScaledDummy(5); - UiSharedService.TextWrapped("Lightless stores downloaded files from paired people permanently. This is to improve loading performance and requiring less downloads. " + + UiSharedService.TextWrapped( + "Lightless stores downloaded files from paired people permanently. This is to improve loading performance and requiring less downloads. " + "The storage governs itself by clearing data beyond the set storage size. Please set the storage size accordingly. It is not necessary to manually clear the storage."); _uiShared.DrawFileScanState(); ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Monitoring Penumbra Folder: " + (_cacheMonitor.PenumbraWatcher?.Path ?? "Not monitoring")); + ImGui.TextUnformatted( + "Monitoring Penumbra Folder: " + (_cacheMonitor.PenumbraWatcher?.Path ?? "Not monitoring")); if (string.IsNullOrEmpty(_cacheMonitor.PenumbraWatcher?.Path)) { ImGui.SameLine(); @@ -751,7 +821,8 @@ public class SettingsUi : WindowMediatorSubscriberBase } ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Monitoring Lightless Storage Folder: " + (_cacheMonitor.LightlessWatcher?.Path ?? "Not monitoring")); + ImGui.TextUnformatted("Monitoring Lightless Storage Folder: " + + (_cacheMonitor.LightlessWatcher?.Path ?? "Not monitoring")); if (string.IsNullOrEmpty(_cacheMonitor.LightlessWatcher?.Path)) { ImGui.SameLine(); @@ -761,6 +832,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _cacheMonitor.StartLightlessWatcher(_configService.Current.CacheFolder); } } + if (_cacheMonitor.LightlessWatcher == null || _cacheMonitor.PenumbraWatcher == null) { if (_uiShared.IconTextButton(FontAwesomeIcon.Play, "Resume Monitoring")) @@ -769,9 +841,11 @@ public class SettingsUi : WindowMediatorSubscriberBase _cacheMonitor.StartPenumbraWatcher(_ipcManager.Penumbra.ModDirectory); _cacheMonitor.InvokeScan(); } + UiSharedService.AttachToolTip("Attempts to resume monitoring for both Penumbra and Lightless Storage. " - + "Resuming the monitoring will also force a full scan to run." + Environment.NewLine - + "If the button remains present after clicking it, consult /xllog for errors"); + + "Resuming the monitoring will also force a full scan to run." + + Environment.NewLine + + "If the button remains present after clicking it, consult /xllog for errors"); } else { @@ -782,32 +856,42 @@ public class SettingsUi : WindowMediatorSubscriberBase _cacheMonitor.StopMonitoring(); } } + UiSharedService.AttachToolTip("Stops the monitoring for both Penumbra and Lightless Storage. " - + "Do not stop the monitoring, unless you plan to move the Penumbra and Lightless Storage folders, to ensure correct functionality of Lightless." + Environment.NewLine - + "If you stop the monitoring to move folders around, resume it after you are finished moving the files." - + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); + + "Do not stop the monitoring, unless you plan to move the Penumbra and Lightless Storage folders, to ensure correct functionality of Lightless." + + Environment.NewLine + + "If you stop the monitoring to move folders around, resume it after you are finished moving the files." + + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); } _uiShared.DrawCacheDirectorySetting(); ImGui.AlignTextToFramePadding(); if (_cacheMonitor.FileCacheSize >= 0) - ImGui.TextUnformatted($"Currently utilized local storage: {UiSharedService.ByteToString(_cacheMonitor.FileCacheSize)}"); + ImGui.TextUnformatted( + $"Currently utilized local storage: {UiSharedService.ByteToString(_cacheMonitor.FileCacheSize)}"); else ImGui.TextUnformatted($"Currently utilized local storage: Calculating..."); - ImGui.TextUnformatted($"Remaining space free on drive: {UiSharedService.ByteToString(_cacheMonitor.FileCacheDriveFree)}"); + ImGui.TextUnformatted( + $"Remaining space free on drive: {UiSharedService.ByteToString(_cacheMonitor.FileCacheDriveFree)}"); bool useFileCompactor = _configService.Current.UseCompactor; bool isLinux = _dalamudUtilService.IsWine; if (!useFileCompactor && !isLinux) { - UiSharedService.ColorTextWrapped("Hint: To free up space when using Lightless consider enabling the File Compactor", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped( + "Hint: To free up space when using Lightless consider enabling the File Compactor", + UIColors.Get("LightlessYellow")); } + if (isLinux || !_cacheMonitor.StorageisNTFS) ImGui.BeginDisabled(); if (ImGui.Checkbox("Use file compactor", ref useFileCompactor)) { _configService.Current.UseCompactor = useFileCompactor; _configService.Save(); } - _uiShared.DrawHelpText("The file compactor can massively reduce your saved files. It might incur a minor penalty on loading files on a slow CPU." + Environment.NewLine + + _uiShared.DrawHelpText( + "The file compactor can massively reduce your saved files. It might incur a minor penalty on loading files on a slow CPU." + + Environment.NewLine + "It is recommended to leave it enabled to save on space."); ImGui.SameLine(); if (!_fileCompactor.MassCompactRunning) @@ -820,8 +904,10 @@ public class SettingsUi : WindowMediatorSubscriberBase _cacheMonitor.RecalculateFileCacheSize(CancellationToken.None); }); } - UiSharedService.AttachToolTip("This will run compression on all files in your current Lightless Storage." + Environment.NewLine - + "You do not need to run this manually if you keep the file compactor enabled."); + + UiSharedService.AttachToolTip("This will run compression on all files in your current Lightless Storage." + + Environment.NewLine + + "You do not need to run this manually if you keep the file compactor enabled."); ImGui.SameLine(); if (_uiShared.IconTextButton(FontAwesomeIcon.File, "Decompact all files in storage")) { @@ -831,26 +917,32 @@ public class SettingsUi : WindowMediatorSubscriberBase _cacheMonitor.RecalculateFileCacheSize(CancellationToken.None); }); } - UiSharedService.AttachToolTip("This will run decompression on all files in your current Lightless Storage."); + + UiSharedService.AttachToolTip( + "This will run decompression on all files in your current Lightless Storage."); } else { - UiSharedService.ColorText($"File compactor currently running ({_fileCompactor.Progress})", UIColors.Get("LightlessYellow")); + UiSharedService.ColorText($"File compactor currently running ({_fileCompactor.Progress})", + UIColors.Get("LightlessYellow")); } + if (isLinux || !_cacheMonitor.StorageisNTFS) { ImGui.EndDisabled(); ImGui.TextUnformatted("The file compactor is only available on Windows and NTFS drives."); } + ImGuiHelpers.ScaledDummy(new Vector2(10, 10)); ImGui.Separator(); if (_uiShared.MediumTreeNode("Storage Validation", UIColors.Get("LightlessYellow"))) { - UiSharedService.TextWrapped("File Storage validation can make sure that all files in your local Lightless Storage are valid. " + - "Run the validation before you clear the Storage for no reason. " + Environment.NewLine + - "This operation, depending on how many files you have in your storage, can take a while and will be CPU and drive intensive."); + UiSharedService.TextWrapped( + "File Storage validation can make sure that all files in your local Lightless Storage are valid. " + + "Run the validation before you clear the Storage for no reason. " + Environment.NewLine + + "This operation, depending on how many files you have in your storage, can take a while and will be CPU and drive intensive."); using (ImRaii.Disabled(_validationTask != null && !_validationTask.IsCompleted)) { if (_uiShared.IconTextButton(FontAwesomeIcon.Check, "Start File Storage Validation")) @@ -859,9 +951,11 @@ public class SettingsUi : WindowMediatorSubscriberBase _validationCts?.Dispose(); _validationCts = new(); var token = _validationCts.Token; - _validationTask = Task.Run(() => _fileCacheManager.ValidateLocalIntegrity(_validationProgress, token)); + _validationTask = Task.Run(() => + _fileCacheManager.ValidateLocalIntegrity(_validationProgress, token)); } } + if (_validationTask != null && !_validationTask.IsCompleted) { ImGui.SameLine(); @@ -877,12 +971,14 @@ public class SettingsUi : WindowMediatorSubscriberBase { if (_validationTask.IsCompleted) { - UiSharedService.TextWrapped($"The storage validation has completed and removed {_validationTask.Result.Count} invalid files from storage."); + UiSharedService.TextWrapped( + $"The storage validation has completed and removed {_validationTask.Result.Count} invalid files from storage."); } else { - UiSharedService.TextWrapped($"Storage validation is running: {_currentProgress.Item1}/{_currentProgress.Item2}"); + UiSharedService.TextWrapped( + $"Storage validation is running: {_currentProgress.Item1}/{_currentProgress.Item2}"); UiSharedService.TextWrapped($"Current item: {_currentProgress.Item3.ResolvedFilepath}"); } } @@ -900,12 +996,15 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.Indent(); ImGui.Checkbox("##readClearCache", ref _readClearCache); ImGui.SameLine(); - UiSharedService.TextWrapped("I understand that: " + Environment.NewLine + "- By clearing the local storage I put the file servers of my connected service under extra strain by having to redownload all data." - + Environment.NewLine + "- This is not a step to try to fix sync issues." - + Environment.NewLine + "- This can make the situation of not getting other players data worse in situations of heavy file server load."); + UiSharedService.TextWrapped("I understand that: " + Environment.NewLine + + "- By clearing the local storage I put the file servers of my connected service under extra strain by having to redownload all data." + + Environment.NewLine + "- This is not a step to try to fix sync issues." + + Environment.NewLine + + "- This can make the situation of not getting other players data worse in situations of heavy file server load."); if (!_readClearCache) ImGui.BeginDisabled(); - if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Clear local storage") && UiSharedService.CtrlPressed() && _readClearCache) + if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Clear local storage") && + UiSharedService.CtrlPressed() && _readClearCache) { _ = Task.Run(() => { @@ -922,9 +1021,14 @@ public class SettingsUi : WindowMediatorSubscriberBase } }); } - UiSharedService.AttachToolTip("You normally do not need to do this. THIS IS NOT SOMETHING YOU SHOULD BE DOING TO TRY TO FIX SYNC ISSUES." + Environment.NewLine - + "This will solely remove all downloaded data from all players and will require you to re-download everything again." + Environment.NewLine - + "Lightless storage is self-clearing and will not surpass the limit you have set it to." + Environment.NewLine + + UiSharedService.AttachToolTip( + "You normally do not need to do this. THIS IS NOT SOMETHING YOU SHOULD BE DOING TO TRY TO FIX SYNC ISSUES." + + Environment.NewLine + + "This will solely remove all downloaded data from all players and will require you to re-download everything again." + + Environment.NewLine + + "Lightless storage is self-clearing and will not surpass the limit you have set it to." + + Environment.NewLine + "If you still think you need to do this hold CTRL while pressing the button."); if (!_readClearCache) ImGui.EndDisabled(); @@ -954,8 +1058,11 @@ public class SettingsUi : WindowMediatorSubscriberBase { if (_uiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard")) { - ImGui.SetClipboardText(UiSharedService.GetNotes(_pairManager.DirectPairs.UnionBy(_pairManager.GroupPairs.SelectMany(p => p.Value), p => p.UserData, UserDataComparer.Instance).ToList())); + ImGui.SetClipboardText(UiSharedService.GetNotes(_pairManager.DirectPairs + .UnionBy(_pairManager.GroupPairs.SelectMany(p => p.Value), p => p.UserData, + UserDataComparer.Instance).ToList())); } + if (_uiShared.IconTextButton(FontAwesomeIcon.FileImport, "Import notes from clipboard")) { _notesSuccessfullyApplied = null; @@ -965,14 +1072,17 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.SameLine(); ImGui.Checkbox("Overwrite existing notes", ref _overwriteExistingLabels); - _uiShared.DrawHelpText("If this option is selected all already existing notes for UIDs will be overwritten by the imported notes."); + _uiShared.DrawHelpText( + "If this option is selected all already existing notes for UIDs will be overwritten by the imported notes."); if (_notesSuccessfullyApplied.HasValue && _notesSuccessfullyApplied.Value) { UiSharedService.ColorTextWrapped("User Notes successfully imported", UIColors.Get("LightlessBlue")); } else if (_notesSuccessfullyApplied.HasValue && !_notesSuccessfullyApplied.Value) { - UiSharedService.ColorTextWrapped("Attempt to import notes from clipboard failed. Check formatting and try again", ImGuiColors.DalamudRed); + UiSharedService.ColorTextWrapped( + "Attempt to import notes from clipboard failed. Check formatting and try again", + ImGuiColors.DalamudRed); } _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); @@ -989,7 +1099,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.OpenPopupOnAdd = openPopupOnAddition; _configService.Save(); } - _uiShared.DrawHelpText("This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs."); + + _uiShared.DrawHelpText( + "This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs."); var autoPopulateNotes = _configService.Current.AutoPopulateEmptyNotesFromCharaName; if (ImGui.Checkbox("Automatically populate notes using player names", ref autoPopulateNotes)) @@ -997,7 +1109,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.AutoPopulateEmptyNotesFromCharaName = autoPopulateNotes; _configService.Save(); } - _uiShared.DrawHelpText("This will automatically populate user notes using the first encountered player name if the note was not set prior"); + + _uiShared.DrawHelpText( + "This will automatically populate user notes using the first encountered player name if the note was not set prior"); _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); ImGui.TreePop(); @@ -1038,6 +1152,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.EnableRightClickMenus = enableRightClickMenu; _configService.Save(); } + _uiShared.DrawHelpText("This will add all Lightless related right click menu entries in the game UI."); if (ImGui.Checkbox("Display status and visible pair count in Server Info Bar", ref enableDtrEntry)) @@ -1045,7 +1160,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.EnableDtrEntry = enableDtrEntry; _configService.Save(); } - _uiShared.DrawHelpText("This will add Lightless connection status and visible pair count in the Server Info Bar.\nYou can further configure this through your Dalamud Settings."); + + _uiShared.DrawHelpText( + "This will add Lightless connection status and visible pair count in the Server Info Bar.\nYou can further configure this through your Dalamud Settings."); using (ImRaii.Disabled(!enableDtrEntry)) { @@ -1087,6 +1204,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { _configService.Current.LightfinderLabelOffsetX = 0; @@ -1095,10 +1213,12 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + if (ImGui.IsItemHovered()) ImGui.SetTooltip("Right click to reset to default."); ImGui.EndDisabled(); - _uiShared.DrawHelpText("Moves the Lightfinder label horizontally on player nameplates.\nUnavailable when automatic alignment is enabled."); + _uiShared.DrawHelpText( + "Moves the Lightfinder label horizontally on player nameplates.\nUnavailable when automatic alignment is enabled."); if (ImGui.SliderInt("Label Offset Y", ref offsetY, -200, 200)) @@ -1109,6 +1229,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { _configService.Current.LightfinderLabelOffsetY = 0; @@ -1117,6 +1238,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + if (ImGui.IsItemHovered()) ImGui.SetTooltip("Right click to reset to default."); _uiShared.DrawHelpText("Moves the Lightfinder label vertically on player nameplates."); @@ -1129,6 +1251,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { _configService.Current.LightfinderLabelScale = 1.0f; @@ -1137,6 +1260,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + if (ImGui.IsItemHovered()) ImGui.SetTooltip("Right click to reset to default."); _uiShared.DrawHelpText("Adjusts the Lightfinder label size for both text and icon modes."); @@ -1151,7 +1275,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } - _uiShared.DrawHelpText("Automatically position the label relative to the in-game nameplate. Turn off to rely entirely on manual offsets."); + + _uiShared.DrawHelpText( + "Automatically position the label relative to the in-game nameplate. Turn off to rely entirely on manual offsets."); if (autoAlign) { @@ -1203,6 +1329,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + _uiShared.DrawHelpText("Toggles your own Lightfinder label."); var showPaired = _configService.Current.LightfinderLabelShowPaired; @@ -1214,6 +1341,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _nameplateHandler.FlagRefresh(); _nameplateService.RequestRedraw(); } + _uiShared.DrawHelpText("Toggles paired player(s) Lightfinder label."); _uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); @@ -1238,6 +1366,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _lightfinderIconPresetIndex = -1; } } + _uiShared.DrawHelpText("Switch between the Lightfinder text label and an icon on nameplates."); if (useIcon) @@ -1311,7 +1440,8 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.Text($"Preview: {previewGlyph}"); - _uiShared.DrawHelpText("Enter a hex code (e.g. E0BB), pick a preset, or paste an icon character directly."); + _uiShared.DrawHelpText( + "Enter a hex code (e.g. E0BB), pick a preset, or paste an icon character directly."); } else { @@ -1335,17 +1465,14 @@ public class SettingsUi : WindowMediatorSubscriberBase ("LightlessPurpleActive", "Primary Purple (Active)", "Active tabs and hover highlights"), ("LightlessPurpleDefault", "Primary Purple (Inactive)", "Inactive tabs and default dividers"), ("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"), - ("PairBlue", "Syncshell Blue", "Syncshell headers, toggle highlights, and moderator actions"), - ("DimRed", "Error Red", "Error and offline colors") }; - if (ImGui.BeginTable("##ColorTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) + if (ImGui.BeginTable("##ColorTable", 3, + ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) { ImGui.TableSetupColumn("Color", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch); @@ -1355,30 +1482,32 @@ public class SettingsUi : WindowMediatorSubscriberBase foreach (var (colorKey, displayName, description) in colorNames) { ImGui.TableNextRow(); - + // color column ImGui.TableSetColumnIndex(0); var currentColor = UIColors.Get(colorKey); var colorToEdit = currentColor; - if (ImGui.ColorEdit4($"##color_{colorKey}", ref colorToEdit, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf)) + if (ImGui.ColorEdit4($"##color_{colorKey}", ref colorToEdit, + ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf)) { UIColors.Set(colorKey, colorToEdit); } + ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(displayName); - + // description column ImGui.TableSetColumnIndex(1); ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(description); - + // actions column ImGui.TableSetColumnIndex(2); using var resetId = ImRaii.PushId($"Reset_{colorKey}"); var availableWidth = ImGui.GetContentRegionAvail().X; var isCustom = UIColors.IsCustom(colorKey); - + using (ImRaii.Disabled(!isCustom)) { using (ImRaii.PushFont(UiBuilder.IconFont)) @@ -1389,9 +1518,12 @@ public class SettingsUi : WindowMediatorSubscriberBase } } } - UiSharedService.AttachToolTip(isCustom ? "Reset this color to default" : "Color is already at default value"); + + UiSharedService.AttachToolTip(isCustom + ? "Reset this color to default" + : "Color is already at default value"); } - + ImGui.EndTable(); } @@ -1400,6 +1532,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { UIColors.ResetAll(); } + _uiShared.DrawHelpText("This will reset all theme colors to their default values"); ImGui.Spacing(); @@ -1413,7 +1546,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.UseColorsInDtr = useColorsInDtr; _configService.Save(); } - _uiShared.DrawHelpText("This will color the Server Info Bar entry based on connection status and visible pairs."); + + _uiShared.DrawHelpText( + "This will color the Server Info Bar entry based on connection status and visible pairs."); using (ImRaii.Disabled(!useColorsInDtr)) { @@ -1457,6 +1592,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); _nameplateService.RequestRedraw(); } + _uiShared.DrawHelpText("This will override the nameplate colors for visible paired players in-game."); using (ImRaii.Disabled(!nameColorsEnabled)) @@ -1468,18 +1604,21 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); _nameplateService.RequestRedraw(); } + if (ImGui.Checkbox("Override friend color", ref isFriendOverride)) { _configService.Current.overrideFriendColor = isFriendOverride; _configService.Save(); _nameplateService.RequestRedraw(); } + if (ImGui.Checkbox("Override party color", ref isPartyOverride)) { _configService.Current.overridePartyColor = isPartyOverride; _configService.Save(); _nameplateService.RequestRedraw(); } + if (ImGui.Checkbox("Override FC tag color", ref isFcTagOverride)) { _configService.Current.overrideFcTagColor = isFcTagOverride; @@ -1507,6 +1646,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.useColoredUIDs = usePairColoredUIDs; _configService.Save(); } + _uiShared.DrawHelpText("This changes the vanity colored UID's in pair list."); _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); @@ -1523,7 +1663,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new RefreshUiMessage()); } - _uiShared.DrawHelpText("This will show all currently visible users in a special 'Visible' group in the main UI."); + + _uiShared.DrawHelpText( + "This will show all currently visible users in a special 'Visible' group in the main UI."); using (ImRaii.Disabled(!showVisibleSeparate)) { @@ -1542,7 +1684,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new RefreshUiMessage()); } - _uiShared.DrawHelpText("This will show all currently offline users in a special 'Offline' group in the main UI."); + + _uiShared.DrawHelpText( + "This will show all currently offline users in a special 'Offline' group in the main UI."); using (ImRaii.Disabled(!showOfflineSeparate)) { @@ -1561,7 +1705,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new RefreshUiMessage()); } - _uiShared.DrawHelpText("This will group up all Syncshells in a special 'All Syncshells' folder in the main UI."); + + _uiShared.DrawHelpText( + "This will group up all Syncshells in a special 'All Syncshells' folder in the main UI."); if (ImGui.Checkbox("Show grouped syncshells in main screen/all syncshells", ref groupedSyncshells)) { @@ -1569,6 +1715,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new RefreshUiMessage()); } + _uiShared.DrawHelpText("This will show grouped syncshells in main screen or group 'All Syncshells'."); if (ImGui.Checkbox("Show player name for visible players", ref showNameInsteadOfNotes)) @@ -1577,7 +1724,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new RefreshUiMessage()); } - _uiShared.DrawHelpText("This will show the character name instead of custom set note when a character is visible"); + + _uiShared.DrawHelpText( + "This will show the character name instead of custom set note when a character is visible"); ImGui.Indent(); if (!_configService.Current.ShowCharacterNameInsteadOfNotesForVisible) ImGui.BeginDisabled(); @@ -1587,6 +1736,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new RefreshUiMessage()); } + _uiShared.DrawHelpText("If you set a note for a player it will be shown instead of the player name"); if (!_configService.Current.ShowCharacterNameInsteadOfNotesForVisible) ImGui.EndDisabled(); ImGui.Unindent(); @@ -1596,6 +1746,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.UseFocusTarget = useFocusTarget; _configService.Save(); } + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); ImGui.TreePop(); } @@ -1609,6 +1760,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ProfilesShow = showProfiles; _configService.Save(); } + _uiShared.DrawHelpText("This will show the configured user profile after a set delay"); ImGui.Indent(); if (!showProfiles) ImGui.BeginDisabled(); @@ -1618,12 +1770,14 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); Mediator.Publish(new CompactUiChange(Vector2.Zero, Vector2.Zero)); } + _uiShared.DrawHelpText("Will show profiles on the right side of the main UI"); if (ImGui.SliderFloat("Hover Delay", ref profileDelay, 1, 10)) { _configService.Current.ProfileDelay = profileDelay; _configService.Save(); } + _uiShared.DrawHelpText("Delay until the profile should be displayed"); if (!showProfiles) ImGui.EndDisabled(); ImGui.Unindent(); @@ -1633,6 +1787,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ProfilesAllowNsfw = showNsfwProfiles; _configService.Save(); } + _uiShared.DrawHelpText("Will show profiles that have the NSFW tag enabled"); _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); @@ -1646,7 +1801,8 @@ public class SettingsUi : WindowMediatorSubscriberBase if (_uiShared.MediumTreeNode("Display", UIColors.Get("LightlessPurple"))) { - _uiShared.DrawHelpText("Notification settings have been moved to the 'Enhanced Notifications' tab for better organization. You can configure where all notifications appear from there."); + _uiShared.DrawHelpText( + "Notification settings have been moved to the 'Enhanced Notifications' tab for better organization. You can configure where all notifications appear from there."); _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); ImGui.TreePop(); @@ -1660,7 +1816,8 @@ public class SettingsUi : WindowMediatorSubscriberBase { _uiShared.UnderlinedBigText("Performance Settings", UIColors.Get("LightlessBlue")); ImGui.Dummy(new Vector2(10)); - UiSharedService.TextWrapped("The configuration options here are to give you more informed warnings and automation when it comes to other performance-intensive synced players."); + UiSharedService.TextWrapped( + "The configuration options here are to give you more informed warnings and automation when it comes to other performance-intensive synced players."); bool showPerformanceIndicator = _playerPerformanceConfigService.Current.ShowPerformanceIndicator; @@ -1671,14 +1828,20 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Current.ShowPerformanceIndicator = showPerformanceIndicator; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("Will show a performance indicator when players exceed defined thresholds in Lightless UI." + Environment.NewLine + "Will use warning thresholds."); + + _uiShared.DrawHelpText( + "Will show a performance indicator when players exceed defined thresholds in Lightless UI." + + Environment.NewLine + "Will use warning thresholds."); bool warnOnExceedingThresholds = _playerPerformanceConfigService.Current.WarnOnExceedingThresholds; - if (ImGui.Checkbox("Warn on loading in players exceeding performance thresholds", ref warnOnExceedingThresholds)) + if (ImGui.Checkbox("Warn on loading in players exceeding performance thresholds", + ref warnOnExceedingThresholds)) { _playerPerformanceConfigService.Current.WarnOnExceedingThresholds = warnOnExceedingThresholds; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("Lightless will print a warning in chat once per session of meeting those people. Will not warn on players with preferred permissions."); + + _uiShared.DrawHelpText( + "Lightless will print a warning in chat once per session of meeting those people. Will not warn on players with preferred permissions."); using (ImRaii.Disabled(!warnOnExceedingThresholds && !showPerformanceIndicator)) { using var indent = ImRaii.PushIndent(); @@ -1688,8 +1851,11 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Current.WarnOnPreferredPermissionsExceedingThresholds = warnOnPref; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("Lightless will also print warnings and show performance indicator for players where you enabled preferred permissions. If warning in general is disabled, this will not produce any warnings."); + + _uiShared.DrawHelpText( + "Lightless will also print warnings and show performance indicator for players where you enabled preferred permissions. If warning in general is disabled, this will not produce any warnings."); } + using (ImRaii.Disabled(!showPerformanceIndicator && !warnOnExceedingThresholds)) { var vram = _playerPerformanceConfigService.Current.VRAMSizeWarningThresholdMiB; @@ -1700,9 +1866,12 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Current.VRAMSizeWarningThresholdMiB = vram; _playerPerformanceConfigService.Save(); } + ImGui.SameLine(); ImGui.Text("(MiB)"); - _uiShared.DrawHelpText("Limit in MiB of approximate VRAM usage to trigger warning or performance indicator on UI." + UiSharedService.TooltipSeparator + _uiShared.DrawHelpText( + "Limit in MiB of approximate VRAM usage to trigger warning or performance indicator on UI." + + UiSharedService.TooltipSeparator + "Default: 375 MiB"); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("Warning Triangle threshold", ref tris)) @@ -1710,9 +1879,12 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Current.TrisWarningThresholdThousands = tris; _playerPerformanceConfigService.Save(); } + ImGui.SameLine(); ImGui.Text("(thousand triangles)"); - _uiShared.DrawHelpText("Limit in approximate used triangles from mods to trigger warning or performance indicator on UI." + UiSharedService.TooltipSeparator + _uiShared.DrawHelpText( + "Limit in approximate used triangles from mods to trigger warning or performance indicator on UI." + + UiSharedService.TooltipSeparator + "Default: 165 thousand"); } @@ -1723,7 +1895,8 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.Separator(); bool autoPause = _playerPerformanceConfigService.Current.AutoPausePlayersExceedingThresholds; - bool autoPauseEveryone = _playerPerformanceConfigService.Current.AutoPausePlayersWithPreferredPermissionsExceedingThresholds; + bool autoPauseEveryone = _playerPerformanceConfigService.Current + .AutoPausePlayersWithPreferredPermissionsExceedingThresholds; bool autoPauseInDuty = _playerPerformanceConfigService.Current.PauseInInstanceDuty; bool autoPauseInCombat = _playerPerformanceConfigService.Current.PauseInCombat; bool autoPauseWhilePerforming = _playerPerformanceConfigService.Current.PauseWhilePerforming; @@ -1735,40 +1908,59 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Current.PauseInCombat = autoPauseInCombat; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("AUTO-ENABLED: Your risk of crashing during a fight increases when this is disabled. For example: VFX mods Loading mid fight can cause a crash." + Environment.NewLine + + _uiShared.DrawHelpText( + "AUTO-ENABLED: Your risk of crashing during a fight increases when this is disabled. For example: VFX mods Loading mid fight can cause a crash." + + Environment.NewLine + UiSharedService.TooltipSeparator + "WARNING: DISABLE AT YOUR OWN RISK."); if (ImGui.Checkbox("Auto pause sync while in Perfomance as Bard", ref autoPauseWhilePerforming)) { _playerPerformanceConfigService.Current.PauseWhilePerforming = autoPauseWhilePerforming; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("AUTO-ENABLED: Your risk of crashing during a performance increases when this is disabled. For example: Some mods can crash you mid performance" + Environment.NewLine + + _uiShared.DrawHelpText( + "AUTO-ENABLED: Your risk of crashing during a performance increases when this is disabled. For example: Some mods can crash you mid performance" + + Environment.NewLine + UiSharedService.TooltipSeparator + "WARNING: DISABLE AT YOUR OWN RISK."); if (ImGui.Checkbox("Auto pause sync while in instances and duties", ref autoPauseInDuty)) { _playerPerformanceConfigService.Current.PauseInInstanceDuty = autoPauseInDuty; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("When enabled, it will automatically pause all players while you are in an instance, such as a dungeon or raid." + Environment.NewLine - + UiSharedService.TooltipSeparator + "Warning: You may have to leave the dungeon to resync with people again"); + + _uiShared.DrawHelpText( + "When enabled, it will automatically pause all players while you are in an instance, such as a dungeon or raid." + + Environment.NewLine + + UiSharedService.TooltipSeparator + + "Warning: You may have to leave the dungeon to resync with people again"); if (ImGui.Checkbox("Automatically pause players exceeding thresholds", ref autoPause)) { _playerPerformanceConfigService.Current.AutoPausePlayersExceedingThresholds = autoPause; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("When enabled, it will automatically pause all players without preferred permissions that exceed the thresholds defined below." + Environment.NewLine + + _uiShared.DrawHelpText( + "When enabled, it will automatically pause all players without preferred permissions that exceed the thresholds defined below." + + Environment.NewLine + "Will print a warning in chat when a player got paused automatically." - + UiSharedService.TooltipSeparator + "Warning: this will not automatically unpause those people again, you will have to do this manually."); + + UiSharedService.TooltipSeparator + + "Warning: this will not automatically unpause those people again, you will have to do this manually."); using (ImRaii.Disabled(!autoPause)) { using var indent = ImRaii.PushIndent(); - if (ImGui.Checkbox("Automatically pause also players with preferred permissions", ref autoPauseEveryone)) + if (ImGui.Checkbox("Automatically pause also players with preferred permissions", + ref autoPauseEveryone)) { - _playerPerformanceConfigService.Current.AutoPausePlayersWithPreferredPermissionsExceedingThresholds = autoPauseEveryone; + _playerPerformanceConfigService.Current + .AutoPausePlayersWithPreferredPermissionsExceedingThresholds = autoPauseEveryone; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("When enabled, will automatically pause all players regardless of preferred permissions that exceed thresholds defined below." + UiSharedService.TooltipSeparator + + + _uiShared.DrawHelpText( + "When enabled, will automatically pause all players regardless of preferred permissions that exceed thresholds defined below." + + UiSharedService.TooltipSeparator + "Warning: this will not automatically unpause those people again, you will have to do this manually."); var vramAuto = _playerPerformanceConfigService.Current.VRAMSizeAutoPauseThresholdMiB; var trisAuto = _playerPerformanceConfigService.Current.TrisAutoPauseThresholdThousands; @@ -1778,9 +1970,12 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Current.VRAMSizeAutoPauseThresholdMiB = vramAuto; _playerPerformanceConfigService.Save(); } + ImGui.SameLine(); ImGui.Text("(MiB)"); - _uiShared.DrawHelpText("When a loading in player and their VRAM usage exceeds this amount, automatically pauses the synced player." + UiSharedService.TooltipSeparator + _uiShared.DrawHelpText( + "When a loading in player and their VRAM usage exceeds this amount, automatically pauses the synced player." + + UiSharedService.TooltipSeparator + "Default: 550 MiB"); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("Auto Pause Triangle threshold", ref trisAuto)) @@ -1788,9 +1983,12 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Current.TrisAutoPauseThresholdThousands = trisAuto; _playerPerformanceConfigService.Save(); } + ImGui.SameLine(); ImGui.Text("(thousand triangles)"); - _uiShared.DrawHelpText("When a loading in player and their triangle count exceeds this amount, automatically pauses the synced player." + UiSharedService.TooltipSeparator + _uiShared.DrawHelpText( + "When a loading in player and their triangle count exceeds this amount, automatically pauses the synced player." + + UiSharedService.TooltipSeparator + "Default: 250 thousand"); } @@ -1804,7 +2002,8 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared.UnderlinedBigText("Whitelisted UIDs", UIColors.Get("LightlessBlue")); ImGuiHelpers.ScaledDummy(5); - UiSharedService.TextWrapped("The entries in the list below will be ignored for all warnings and auto pause operations."); + UiSharedService.TextWrapped( + "The entries in the list below will be ignored for all warnings and auto pause operations."); ImGui.Dummy(new Vector2(10)); ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.InputText("##ignoreuid", ref _uidToAddForIgnore, 20); @@ -1813,14 +2012,17 @@ public class SettingsUi : WindowMediatorSubscriberBase { if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, "Add UID/Vanity ID to whitelist")) { - if (!_playerPerformanceConfigService.Current.UIDsToIgnore.Contains(_uidToAddForIgnore, StringComparer.Ordinal)) + if (!_playerPerformanceConfigService.Current.UIDsToIgnore.Contains(_uidToAddForIgnore, + StringComparer.Ordinal)) { _playerPerformanceConfigService.Current.UIDsToIgnore.Add(_uidToAddForIgnore); _playerPerformanceConfigService.Save(); } + _uidToAddForIgnore = string.Empty; } } + _uiShared.DrawHelpText("Hint: UIDs are case sensitive."); var playerList = _playerPerformanceConfigService.Current.UIDsToIgnore; ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); @@ -1838,6 +2040,7 @@ public class SettingsUi : WindowMediatorSubscriberBase } } } + using (ImRaii.Disabled(_selectedEntry == -1)) { if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete selected UID")) @@ -1868,7 +2071,8 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared.DrawHelpText("Completely deletes all your uploaded files on the service."); - if (ImGui.BeginPopupModal("Delete all your files?", ref _deleteFilesPopupModalShown, UiSharedService.PopupWindowFlags)) + if (ImGui.BeginPopupModal("Delete all your files?", ref _deleteFilesPopupModalShown, + UiSharedService.PopupWindowFlags)) { UiSharedService.TextWrapped( "All your own uploaded files on the service will be deleted.\nThis operation cannot be undone."); @@ -1877,7 +2081,7 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.Spacing(); var buttonSize = (ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - - ImGui.GetStyle().ItemSpacing.X) / 2; + ImGui.GetStyle().ItemSpacing.X) / 2; if (ImGui.Button("Delete everything", new Vector2(buttonSize, 0))) { @@ -1895,6 +2099,7 @@ public class SettingsUi : WindowMediatorSubscriberBase UiSharedService.SetScaledWindowSize(325); ImGui.EndPopup(); } + ImGui.SameLine(); if (ImGui.Button("Delete account")) { @@ -1904,7 +2109,8 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared.DrawHelpText("Completely deletes your account and all uploaded files to the service."); - if (ImGui.BeginPopupModal("Delete your account?", ref _deleteAccountPopupModalShown, UiSharedService.PopupWindowFlags)) + if (ImGui.BeginPopupModal("Delete your account?", ref _deleteAccountPopupModalShown, + UiSharedService.PopupWindowFlags)) { UiSharedService.TextWrapped( "Your account and all associated files and data on the service will be deleted."); @@ -1951,14 +2157,18 @@ public class SettingsUi : WindowMediatorSubscriberBase { _serverConfigurationManager.SendCensusData = sendCensus; } - _uiShared.DrawHelpText("This will allow sending census data to the currently connected service." + UiSharedService.TooltipSeparator - + "Census data contains:" + Environment.NewLine - + "- Current World" + Environment.NewLine - + "- Current Gender" + Environment.NewLine - + "- Current Race" + Environment.NewLine - + "- Current Clan (this is not your Free Company, this is e.g. Keeper or Seeker for Miqo'te)" + UiSharedService.TooltipSeparator - + "The census data is only saved temporarily and will be removed from the server on disconnect. It is stored temporarily associated with your UID while you are connected." + UiSharedService.TooltipSeparator - + "If you do not wish to participate in the statistical census, untick this box and reconnect to the server."); + + _uiShared.DrawHelpText("This will allow sending census data to the currently connected service." + + UiSharedService.TooltipSeparator + + "Census data contains:" + Environment.NewLine + + "- Current World" + Environment.NewLine + + "- Current Gender" + Environment.NewLine + + "- Current Race" + Environment.NewLine + + "- Current Clan (this is not your Free Company, this is e.g. Keeper or Seeker for Miqo'te)" + + UiSharedService.TooltipSeparator + + "The census data is only saved temporarily and will be removed from the server on disconnect. It is stored temporarily associated with your UID while you are connected." + + UiSharedService.TooltipSeparator + + "If you do not wish to participate in the statistical census, untick this box and reconnect to the server."); ImGuiHelpers.ScaledDummy(new Vector2(10, 10)); var idx = _uiShared.DrawServiceSelection(); @@ -1975,7 +2185,9 @@ public class SettingsUi : WindowMediatorSubscriberBase var selectedServer = _serverConfigurationManager.GetServerByIndex(idx); if (selectedServer == _serverConfigurationManager.CurrentServer) { - UiSharedService.ColorTextWrapped("For any changes to be applied to the current service you need to reconnect to the service.", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped( + "For any changes to be applied to the current service you need to reconnect to the service.", + UIColors.Get("LightlessYellow")); } bool useOauth = selectedServer.UseOAuth2; @@ -1986,30 +2198,41 @@ public class SettingsUi : WindowMediatorSubscriberBase { if (selectedServer.SecretKeys.Any() || useOauth) { - UiSharedService.ColorTextWrapped("Characters listed here will automatically connect to the selected Lightless service with the settings as provided below." + - " Make sure to enter the character names correctly or use the 'Add current character' button at the bottom.", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped( + "Characters listed here will automatically connect to the selected Lightless service with the settings as provided below." + + " Make sure to enter the character names correctly or use the 'Add current character' button at the bottom.", + UIColors.Get("LightlessYellow")); int i = 0; _uiShared.DrawUpdateOAuthUIDsButton(selectedServer); if (selectedServer.UseOAuth2 && !string.IsNullOrEmpty(selectedServer.OAuthToken)) { - bool hasSetSecretKeysButNoUid = selectedServer.Authentications.Exists(u => u.SecretKeyIdx != -1 && string.IsNullOrEmpty(u.UID)); + bool hasSetSecretKeysButNoUid = + selectedServer.Authentications.Exists(u => + u.SecretKeyIdx != -1 && string.IsNullOrEmpty(u.UID)); if (hasSetSecretKeysButNoUid) { ImGui.Dummy(new(5f, 5f)); - UiSharedService.TextWrapped("Some entries have been detected that have previously been assigned secret keys but not UIDs. " + + UiSharedService.TextWrapped( + "Some entries have been detected that have previously been assigned secret keys but not UIDs. " + "Press this button below to attempt to convert those entries."); - using (ImRaii.Disabled(_secretKeysConversionTask != null && !_secretKeysConversionTask.IsCompleted)) + using (ImRaii.Disabled(_secretKeysConversionTask != null && + !_secretKeysConversionTask.IsCompleted)) { - if (_uiShared.IconTextButton(FontAwesomeIcon.ArrowsLeftRight, "Try to Convert Secret Keys to UIDs")) + if (_uiShared.IconTextButton(FontAwesomeIcon.ArrowsLeftRight, + "Try to Convert Secret Keys to UIDs")) { - _secretKeysConversionTask = ConvertSecretKeysToUIDs(selectedServer, _secretKeysConversionCts.Token); + _secretKeysConversionTask = + ConvertSecretKeysToUIDs(selectedServer, _secretKeysConversionCts.Token); } } + if (_secretKeysConversionTask != null && !_secretKeysConversionTask.IsCompleted) { - UiSharedService.ColorTextWrapped("Converting Secret Keys to UIDs", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped("Converting Secret Keys to UIDs", + UIColors.Get("LightlessYellow")); } + if (_secretKeysConversionTask != null && _secretKeysConversionTask.IsCompletedSuccessfully) { Vector4? textColor = null; @@ -2017,10 +2240,12 @@ public class SettingsUi : WindowMediatorSubscriberBase { textColor = UIColors.Get("LightlessYellow"); } + if (!_secretKeysConversionTask.Result.Success) { textColor = ImGuiColors.DalamudRed; } + string text = $"Conversion has completed: {_secretKeysConversionTask.Result.Result}"; if (textColor == null) { @@ -2030,49 +2255,70 @@ public class SettingsUi : WindowMediatorSubscriberBase { UiSharedService.ColorTextWrapped(text, textColor!.Value); } - if (!_secretKeysConversionTask.Result.Success || _secretKeysConversionTask.Result.PartialSuccess) + + if (!_secretKeysConversionTask.Result.Success || + _secretKeysConversionTask.Result.PartialSuccess) { - UiSharedService.TextWrapped("In case of conversion failures, please set the UIDs for the failed conversions manually."); + UiSharedService.TextWrapped( + "In case of conversion failures, please set the UIDs for the failed conversions manually."); } } } } + ImGui.Separator(); string youName = _dalamudUtilService.GetPlayerName(); uint youWorld = _dalamudUtilService.GetHomeWorldId(); ulong youCid = _dalamudUtilService.GetCID(); - if (!selectedServer.Authentications.Exists(a => string.Equals(a.CharacterName, youName, StringComparison.Ordinal) && a.WorldId == youWorld)) + if (!selectedServer.Authentications.Exists(a => + string.Equals(a.CharacterName, youName, StringComparison.Ordinal) && a.WorldId == youWorld)) { _uiShared.BigText("Your Character is not Configured", ImGuiColors.DalamudRed); - UiSharedService.ColorTextWrapped("You have currently no character configured that corresponds to your current name and world.", ImGuiColors.DalamudRed); + UiSharedService.ColorTextWrapped( + "You have currently no character configured that corresponds to your current name and world.", + ImGuiColors.DalamudRed); var authWithCid = selectedServer.Authentications.Find(f => f.LastSeenCID == youCid); if (authWithCid != null) { ImGuiHelpers.ScaledDummy(5); - UiSharedService.ColorText("A potential rename/world change from this character was detected:", UIColors.Get("LightlessYellow")); + UiSharedService.ColorText( + "A potential rename/world change from this character was detected:", + UIColors.Get("LightlessYellow")); using (ImRaii.PushIndent(10f)) - UiSharedService.ColorText("Entry: " + authWithCid.CharacterName + " - " + _dalamudUtilService.WorldData.Value[(ushort)authWithCid.WorldId], UIColors.Get("LightlessBlue")); - UiSharedService.ColorText("Press the button below to adjust that entry to your current character:", UIColors.Get("LightlessYellow")); + UiSharedService.ColorText( + "Entry: " + authWithCid.CharacterName + " - " + + _dalamudUtilService.WorldData.Value[(ushort)authWithCid.WorldId], + UIColors.Get("LightlessBlue")); + UiSharedService.ColorText( + "Press the button below to adjust that entry to your current character:", + UIColors.Get("LightlessYellow")); using (ImRaii.PushIndent(10f)) - UiSharedService.ColorText("Current: " + youName + " - " + _dalamudUtilService.WorldData.Value[(ushort)youWorld], UIColors.Get("LightlessBlue")); + UiSharedService.ColorText( + "Current: " + youName + " - " + + _dalamudUtilService.WorldData.Value[(ushort)youWorld], + UIColors.Get("LightlessBlue")); ImGuiHelpers.ScaledDummy(5); - if (_uiShared.IconTextButton(FontAwesomeIcon.ArrowRight, "Update Entry to Current Character")) + if (_uiShared.IconTextButton(FontAwesomeIcon.ArrowRight, + "Update Entry to Current Character")) { authWithCid.CharacterName = youName; authWithCid.WorldId = youWorld; _serverConfigurationManager.Save(); } } + ImGuiHelpers.ScaledDummy(5); ImGui.Separator(); ImGuiHelpers.ScaledDummy(5); } + foreach (var item in selectedServer.Authentications.ToList()) { using var charaId = ImRaii.PushId("selectedChara" + i); var worldIdx = (ushort)item.WorldId; - var data = _uiShared.WorldData.OrderBy(u => u.Value, StringComparer.Ordinal).ToDictionary(k => k.Key, k => k.Value); + var data = _uiShared.WorldData.OrderBy(u => u.Value, StringComparer.Ordinal) + .ToDictionary(k => k.Key, k => k.Value); if (!data.TryGetValue(worldIdx, out string? worldPreview)) { worldPreview = data.First().Value; @@ -2096,24 +2342,31 @@ public class SettingsUi : WindowMediatorSubscriberBase { thisIsYou = true; } + bool misManaged = false; - if (selectedServer.UseOAuth2 && !string.IsNullOrEmpty(selectedServer.OAuthToken) && string.IsNullOrEmpty(item.UID)) + if (selectedServer.UseOAuth2 && !string.IsNullOrEmpty(selectedServer.OAuthToken) && + string.IsNullOrEmpty(item.UID)) { misManaged = true; } + if (!selectedServer.UseOAuth2 && item.SecretKeyIdx == -1) { misManaged = true; } + Vector4 color = UIColors.Get("LightlessBlue"); string text = thisIsYou ? "Your Current Character" : string.Empty; if (misManaged) { - text += " [MISMANAGED (" + (selectedServer.UseOAuth2 ? "No UID Set" : "No Secret Key Set") + ")]"; + text += " [MISMANAGED (" + (selectedServer.UseOAuth2 ? "No UID Set" : "No Secret Key Set") + + ")]"; color = ImGuiColors.DalamudRed; } - if (selectedServer.Authentications.Where(e => e != item).Any(e => string.Equals(e.CharacterName, item.CharacterName, StringComparison.Ordinal) - && e.WorldId == item.WorldId)) + + if (selectedServer.Authentications.Where(e => e != item).Any(e => + string.Equals(e.CharacterName, item.CharacterName, StringComparison.Ordinal) + && e.WorldId == item.WorldId)) { text += " [DUPLICATE]"; color = ImGuiColors.DalamudRed; @@ -2140,11 +2393,16 @@ public class SettingsUi : WindowMediatorSubscriberBase item.WorldId = w.Key; _serverConfigurationManager.Save(); } - }, EqualityComparer>.Default.Equals(data.FirstOrDefault(f => f.Key == worldIdx), default) ? data.First() : data.First(f => f.Key == worldIdx)); + }, + EqualityComparer>.Default.Equals( + data.FirstOrDefault(f => f.Key == worldIdx), default) + ? data.First() + : data.First(f => f.Key == worldIdx)); if (!useOauth) { - _uiShared.DrawCombo("Secret Key###" + item.CharacterName + i, keys, (w) => w.Value.FriendlyName, + _uiShared.DrawCombo("Secret Key###" + item.CharacterName + i, keys, + (w) => w.Value.FriendlyName, (w) => { if (w.Key != item.SecretKeyIdx) @@ -2152,20 +2410,28 @@ public class SettingsUi : WindowMediatorSubscriberBase item.SecretKeyIdx = w.Key; _serverConfigurationManager.Save(); } - }, EqualityComparer>.Default.Equals(keys.FirstOrDefault(f => f.Key == item.SecretKeyIdx), default) ? keys.First() : keys.First(f => f.Key == item.SecretKeyIdx)); + }, + EqualityComparer>.Default.Equals( + keys.FirstOrDefault(f => f.Key == item.SecretKeyIdx), default) + ? keys.First() + : keys.First(f => f.Key == item.SecretKeyIdx)); } else { _uiShared.DrawUIDComboForAuthentication(i, item, selectedServer.ServerUri, _logger); } + bool isAutoLogin = item.AutoLogin; if (ImGui.Checkbox("Automatically login to Lightless", ref isAutoLogin)) { item.AutoLogin = isAutoLogin; _serverConfigurationManager.Save(); } - _uiShared.DrawHelpText("When enabled and logging into this character in XIV, Lightless will automatically connect to the current service."); - if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Character") && UiSharedService.CtrlPressed()) + + _uiShared.DrawHelpText( + "When enabled and logging into this character in XIV, Lightless will automatically connect to the current service."); + if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Character") && + UiSharedService.CtrlPressed()) _serverConfigurationManager.RemoveCharacterFromServer(idx, item); UiSharedService.AttachToolTip("Hold CTRL to delete this entry."); @@ -2181,13 +2447,15 @@ public class SettingsUi : WindowMediatorSubscriberBase if (selectedServer.Authentications.Any()) ImGui.Separator(); - if (!selectedServer.Authentications.Exists(c => string.Equals(c.CharacterName, youName, StringComparison.Ordinal) - && c.WorldId == youWorld)) + if (!selectedServer.Authentications.Exists(c => + string.Equals(c.CharacterName, youName, StringComparison.Ordinal) + && c.WorldId == youWorld)) { if (_uiShared.IconTextButton(FontAwesomeIcon.User, "Add current character")) { _serverConfigurationManager.AddCurrentCharacterToServer(idx); } + ImGui.SameLine(); } @@ -2198,7 +2466,8 @@ public class SettingsUi : WindowMediatorSubscriberBase } else { - UiSharedService.ColorTextWrapped("You need to add a Secret Key first before adding Characters.", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped("You need to add a Secret Key first before adding Characters.", + UIColors.Get("LightlessYellow")); } ImGui.EndTabItem(); @@ -2215,24 +2484,29 @@ public class SettingsUi : WindowMediatorSubscriberBase item.Value.FriendlyName = friendlyName; _serverConfigurationManager.Save(); } + var key = item.Value.Key; if (ImGui.InputText("Secret Key", ref key, 64)) { item.Value.Key = key; _serverConfigurationManager.Save(); } + if (!selectedServer.Authentications.Exists(p => p.SecretKeyIdx == item.Key)) { - if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Secret Key") && UiSharedService.CtrlPressed()) + if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Secret Key") && + UiSharedService.CtrlPressed()) { selectedServer.SecretKeys.Remove(item.Key); _serverConfigurationManager.Save(); } + UiSharedService.AttachToolTip("Hold CTRL to delete this secret key entry"); } else { - UiSharedService.ColorTextWrapped("This key is in use and cannot be deleted", UIColors.Get("LightlessYellow")); + UiSharedService.ColorTextWrapped("This key is in use and cannot be deleted", + UIColors.Get("LightlessYellow")); } if (item.Key != selectedServer.SecretKeys.Keys.LastOrDefault()) @@ -2242,10 +2516,9 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.Separator(); if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, "Add new Secret Key")) { - selectedServer.SecretKeys.Add(selectedServer.SecretKeys.Any() ? selectedServer.SecretKeys.Max(p => p.Key) + 1 : 0, new SecretKey() - { - FriendlyName = "New Secret Key", - }); + selectedServer.SecretKeys.Add( + selectedServer.SecretKeys.Any() ? selectedServer.SecretKeys.Max(p => p.Key) + 1 : 0, + new SecretKey() { FriendlyName = "New Secret Key", }); _serverConfigurationManager.Save(); } @@ -2263,6 +2536,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { selectedServer.ServerUri = serverUri; } + if (isMain) { _uiShared.DrawHelpText("You cannot edit the URI of the main service."); @@ -2273,6 +2547,7 @@ public class SettingsUi : WindowMediatorSubscriberBase selectedServer.ServerName = serverName; _serverConfigurationManager.Save(); } + if (isMain) { _uiShared.DrawHelpText("You cannot edit the name of the main service."); @@ -2280,12 +2555,16 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.SetNextItemWidth(200); var serverTransport = _serverConfigurationManager.GetTransport(); - _uiShared.DrawCombo("Server Transport Type", Enum.GetValues().Where(t => t != HttpTransportType.None), + _uiShared.DrawCombo("Server Transport Type", + Enum.GetValues().Where(t => t != HttpTransportType.None), (v) => v.ToString(), onSelected: (t) => _serverConfigurationManager.SetTransportType(t), serverTransport); - _uiShared.DrawHelpText("You normally do not need to change this, if you don't know what this is or what it's for, keep it to WebSockets." + Environment.NewLine - + "If you run into connection issues with e.g. VPNs, try ServerSentEvents first before trying out LongPolling." + UiSharedService.TooltipSeparator + _uiShared.DrawHelpText( + "You normally do not need to change this, if you don't know what this is or what it's for, keep it to WebSockets." + + Environment.NewLine + + "If you run into connection issues with e.g. VPNs, try ServerSentEvents first before trying out LongPolling." + + UiSharedService.TooltipSeparator + "Note: if the server does not support a specific Transport Type it will fall through to the next automatically: WebSockets > ServerSentEvents > LongPolling"); if (_dalamudUtilService.IsWine) @@ -2296,7 +2575,9 @@ public class SettingsUi : WindowMediatorSubscriberBase selectedServer.ForceWebSockets = forceWebSockets; _serverConfigurationManager.Save(); } - _uiShared.DrawHelpText("On wine, Lightless will automatically fall back to ServerSentEvents/LongPolling, even if WebSockets is selected. " + + _uiShared.DrawHelpText( + "On wine, Lightless will automatically fall back to ServerSentEvents/LongPolling, even if WebSockets is selected. " + "WebSockets are known to crash XIV entirely on wine 8.5 shipped with Dalamud. " + "Only enable this if you are not running wine 8.5." + Environment.NewLine + "Note: If the issue gets resolved at some point this option will be removed."); @@ -2309,20 +2590,26 @@ public class SettingsUi : WindowMediatorSubscriberBase selectedServer.UseOAuth2 = useOauth; _serverConfigurationManager.Save(); } - _uiShared.DrawHelpText("Use Discord OAuth2 Authentication to identify with this server instead of secret keys"); + + _uiShared.DrawHelpText( + "Use Discord OAuth2 Authentication to identify with this server instead of secret keys"); if (useOauth) { _uiShared.DrawOAuth(selectedServer); if (string.IsNullOrEmpty(_serverConfigurationManager.GetDiscordUserFromToken(selectedServer))) { ImGuiHelpers.ScaledDummy(10f); - UiSharedService.ColorTextWrapped("You have enabled OAuth2 but it is not linked. Press the buttons Check, then Authenticate to link properly.", ImGuiColors.DalamudRed); + UiSharedService.ColorTextWrapped( + "You have enabled OAuth2 but it is not linked. Press the buttons Check, then Authenticate to link properly.", + ImGuiColors.DalamudRed); } + if (!string.IsNullOrEmpty(_serverConfigurationManager.GetDiscordUserFromToken(selectedServer)) && selectedServer.Authentications.TrueForAll(u => string.IsNullOrEmpty(u.UID))) { ImGuiHelpers.ScaledDummy(10f); - UiSharedService.ColorTextWrapped("You have enabled OAuth2 but no characters configured. Set the correct UIDs for your characters in \"Character Management\".", + UiSharedService.ColorTextWrapped( + "You have enabled OAuth2 but no characters configured. Set the correct UIDs for your characters in \"Character Management\".", ImGuiColors.DalamudRed); } } @@ -2330,10 +2617,12 @@ public class SettingsUi : WindowMediatorSubscriberBase if (!isMain && selectedServer != _serverConfigurationManager.CurrentServer) { ImGui.Separator(); - if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Service") && UiSharedService.CtrlPressed()) + if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Service") && + UiSharedService.CtrlPressed()) { _serverConfigurationManager.DeleteServer(selectedServer); } + _uiShared.DrawHelpText("Hold CTRL to delete this service"); } @@ -2345,26 +2634,35 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared.BigText("Default Permission Settings"); if (selectedServer == _serverConfigurationManager.CurrentServer && _apiController.IsConnected) { - UiSharedService.TextWrapped("Note: The default permissions settings here are not applied retroactively to existing pairs or joined Syncshells."); - UiSharedService.TextWrapped("Note: The default permissions settings here are sent and stored on the connected service."); + UiSharedService.TextWrapped( + "Note: The default permissions settings here are not applied retroactively to existing pairs or joined Syncshells."); + UiSharedService.TextWrapped( + "Note: The default permissions settings here are sent and stored on the connected service."); ImGuiHelpers.ScaledDummy(5f); var perms = _apiController.DefaultPermissions!; bool individualIsSticky = perms.IndividualIsSticky; bool disableIndividualSounds = perms.DisableIndividualSounds; bool disableIndividualAnimations = perms.DisableIndividualAnimations; bool disableIndividualVFX = perms.DisableIndividualVFX; - if (ImGui.Checkbox("Individually set permissions become preferred permissions", ref individualIsSticky)) + if (ImGui.Checkbox("Individually set permissions become preferred permissions", + ref individualIsSticky)) { perms.IndividualIsSticky = individualIsSticky; _ = _apiController.UserUpdateDefaultPermissions(perms); } - _uiShared.DrawHelpText("The preferred attribute means that the permissions to that user will never change through any of your permission changes to Syncshells " + + + _uiShared.DrawHelpText( + "The preferred attribute means that the permissions to that user will never change through any of your permission changes to Syncshells " + "(i.e. if you have paused one specific user in a Syncshell and they become preferred permissions, then pause and unpause the same Syncshell, the user will remain paused - " + - "if a user does not have preferred permissions, it will follow the permissions of the Syncshell and be unpaused)." + Environment.NewLine + Environment.NewLine + + "if a user does not have preferred permissions, it will follow the permissions of the Syncshell and be unpaused)." + + Environment.NewLine + Environment.NewLine + "This setting means:" + Environment.NewLine + - " - All new individual pairs get their permissions defaulted to preferred permissions." + Environment.NewLine + - " - All individually set permissions for any pair will also automatically become preferred permissions. This includes pairs in Syncshells." + Environment.NewLine + Environment.NewLine + - "It is possible to remove or set the preferred permission state for any pair at any time." + Environment.NewLine + Environment.NewLine + + " - All new individual pairs get their permissions defaulted to preferred permissions." + + Environment.NewLine + + " - All individually set permissions for any pair will also automatically become preferred permissions. This includes pairs in Syncshells." + + Environment.NewLine + Environment.NewLine + + "It is possible to remove or set the preferred permission state for any pair at any time." + + Environment.NewLine + Environment.NewLine + "If unsure, leave this setting off."); ImGuiHelpers.ScaledDummy(3f); @@ -2373,18 +2671,21 @@ public class SettingsUi : WindowMediatorSubscriberBase perms.DisableIndividualSounds = disableIndividualSounds; _ = _apiController.UserUpdateDefaultPermissions(perms); } + _uiShared.DrawHelpText("This setting will disable sound sync for all new individual pairs."); if (ImGui.Checkbox("Disable individual pair animations", ref disableIndividualAnimations)) { perms.DisableIndividualAnimations = disableIndividualAnimations; _ = _apiController.UserUpdateDefaultPermissions(perms); } + _uiShared.DrawHelpText("This setting will disable animation sync for all new individual pairs."); if (ImGui.Checkbox("Disable individual pair VFX", ref disableIndividualVFX)) { perms.DisableIndividualVFX = disableIndividualVFX; _ = _apiController.UserUpdateDefaultPermissions(perms); } + _uiShared.DrawHelpText("This setting will disable VFX sync for all new individual pairs."); ImGuiHelpers.ScaledDummy(5f); bool disableGroundSounds = perms.DisableGroupSounds; @@ -2395,28 +2696,36 @@ public class SettingsUi : WindowMediatorSubscriberBase perms.DisableGroupSounds = disableGroundSounds; _ = _apiController.UserUpdateDefaultPermissions(perms); } - _uiShared.DrawHelpText("This setting will disable sound sync for all non-sticky pairs in newly joined syncshells."); + + _uiShared.DrawHelpText( + "This setting will disable sound sync for all non-sticky pairs in newly joined syncshells."); if (ImGui.Checkbox("Disable Syncshell pair animations", ref disableGroupAnimations)) { perms.DisableGroupAnimations = disableGroupAnimations; _ = _apiController.UserUpdateDefaultPermissions(perms); } - _uiShared.DrawHelpText("This setting will disable animation sync for all non-sticky pairs in newly joined syncshells."); + + _uiShared.DrawHelpText( + "This setting will disable animation sync for all non-sticky pairs in newly joined syncshells."); if (ImGui.Checkbox("Disable Syncshell pair VFX", ref disableGroupVFX)) { perms.DisableGroupVFX = disableGroupVFX; _ = _apiController.UserUpdateDefaultPermissions(perms); } - _uiShared.DrawHelpText("This setting will disable VFX sync for all non-sticky pairs in newly joined syncshells."); + + _uiShared.DrawHelpText( + "This setting will disable VFX sync for all non-sticky pairs in newly joined syncshells."); } else { UiSharedService.ColorTextWrapped("Default Permission Settings unavailable for this service. " + - "You need to connect to this service to change the default permissions since they are stored on the service.", UIColors.Get("LightlessYellow")); + "You need to connect to this service to change the default permissions since they are stored on the service.", + UIColors.Get("LightlessYellow")); } ImGui.EndTabItem(); } + ImGui.EndTabBar(); } @@ -2427,10 +2736,13 @@ public class SettingsUi : WindowMediatorSubscriberBase private Task<(bool Success, bool PartialSuccess, string Result)>? _secretKeysConversionTask = null; private CancellationTokenSource _secretKeysConversionCts = new CancellationTokenSource(); - private async Task<(bool Success, bool partialSuccess, string Result)> ConvertSecretKeysToUIDs(ServerStorage serverStorage, CancellationToken token) + private async Task<(bool Success, bool partialSuccess, string Result)> ConvertSecretKeysToUIDs( + ServerStorage serverStorage, CancellationToken token) { - List failedConversions = serverStorage.Authentications.Where(u => u.SecretKeyIdx == -1 && string.IsNullOrEmpty(u.UID)).ToList(); - List conversionsToAttempt = serverStorage.Authentications.Where(u => u.SecretKeyIdx != -1 && string.IsNullOrEmpty(u.UID)).ToList(); + List failedConversions = serverStorage.Authentications + .Where(u => u.SecretKeyIdx == -1 && string.IsNullOrEmpty(u.UID)).ToList(); + List conversionsToAttempt = serverStorage.Authentications + .Where(u => u.SecretKeyIdx != -1 && string.IsNullOrEmpty(u.UID)).ToList(); List successfulConversions = []; Dictionary> secretKeyMapping = new(StringComparer.Ordinal); foreach (var authEntry in conversionsToAttempt) @@ -2451,7 +2763,9 @@ public class SettingsUi : WindowMediatorSubscriberBase if (secretKeyMapping.Count == 0) { - return (false, false, $"Failed to convert {failedConversions.Count} entries: " + string.Join(", ", failedConversions.Select(k => k.CharacterName))); + return (false, false, + $"Failed to convert {failedConversions.Count} entries: " + + string.Join(", ", failedConversions.Select(k => k.CharacterName))); } var baseUri = serverStorage.ServerUri.Replace("wss://", "https://").Replace("ws://", "http://"); @@ -2462,8 +2776,10 @@ public class SettingsUi : WindowMediatorSubscriberBase requestMessage.Content = requestContent; using var response = await _httpClient.SendAsync(requestMessage, token).ConfigureAwait(false); - Dictionary? secretKeyUidMapping = await JsonSerializer.DeserializeAsync> - (await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false), cancellationToken: token).ConfigureAwait(false); + Dictionary? secretKeyUidMapping = await JsonSerializer + .DeserializeAsync> + (await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false), cancellationToken: token) + .ConfigureAwait(false); if (secretKeyUidMapping == null) { return (false, false, $"Failed to parse the server response. Failed to convert all entries."); @@ -2501,7 +2817,8 @@ public class SettingsUi : WindowMediatorSubscriberBase private static string GetLightfinderPresetGlyph(int index) { - return NameplateHandler.NormalizeIconGlyph(SeIconCharExtensions.ToIconString(LightfinderIconPresets[index].Icon)); + return NameplateHandler.NormalizeIconGlyph( + SeIconCharExtensions.ToIconString(LightfinderIconPresets[index].Icon)); } private void RefreshLightfinderIconState() @@ -2542,7 +2859,8 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.SameLine(); ImGui.TextUnformatted("("); ImGui.SameLine(); - ImGui.TextColored(UIColors.Get("LightlessBlue"), _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture)); + ImGui.TextColored(UIColors.Get("LightlessBlue"), + _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture)); ImGui.SameLine(); ImGui.TextUnformatted("Users Online"); ImGui.SameLine(); @@ -2556,6 +2874,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { Util.OpenLink("https://discord.gg/mpNdkrTRjW"); } + ImGui.Separator(); if (ImGui.BeginTabBar("mainTabBar")) { @@ -2628,284 +2947,317 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.UseLightlessNotifications = useLightlessNotifications; _configService.Save(); } - _uiShared.DrawHelpText("Enable modern notifications with interactive buttons, animations, and progress tracking. Disable for classic Dalamud toast/chat notifications."); - + + _uiShared.DrawHelpText( + "Enable modern notifications with interactive buttons, animations, and progress tracking. Disable for classic Dalamud toast/chat notifications."); + ImGuiHelpers.ScaledDummy(5); ImGui.Separator(); - _uiShared.UnderlinedBigText("Notification Locations", UIColors.Get("LightlessBlue")); - ImGuiHelpers.ScaledDummy(5); - UiSharedService.ColorTextWrapped("Choose where each notification type appears.", ImGuiColors.DalamudGrey); - ImGuiHelpers.ScaledDummy(5); - - if (useLightlessNotifications) + if (_uiShared.MediumTreeNode("Notification Locations", UIColors.Get("LightlessPurple"))) { - // Lightless notification locations - ImGui.Indent(); - - var lightlessLocations = GetLightlessNotificationLocations(); - - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Info Notifications:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - _uiShared.DrawCombo("###enhanced_info", lightlessLocations, GetNotificationLocationLabel, (location) => - { - _configService.Current.LightlessInfoNotification = location; - _configService.Save(); - }, _configService.Current.LightlessInfoNotification); - - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Warning Notifications:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - _uiShared.DrawCombo("###enhanced_warning", lightlessLocations, GetNotificationLocationLabel, (location) => - { - _configService.Current.LightlessWarningNotification = location; - _configService.Save(); - }, _configService.Current.LightlessWarningNotification); - - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Error Notifications:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - _uiShared.DrawCombo("###enhanced_error", lightlessLocations, GetNotificationLocationLabel, (location) => - { - _configService.Current.LightlessErrorNotification = location; - _configService.Save(); - }, _configService.Current.LightlessErrorNotification); - - ImGuiHelpers.ScaledDummy(3); - _uiShared.DrawHelpText("Special notification types:"); - - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Pair Request Notifications:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - _uiShared.DrawCombo("###enhanced_pairrequest", lightlessLocations, GetNotificationLocationLabel, (location) => - { - _configService.Current.LightlessPairRequestNotification = location; - _configService.Save(); - }, _configService.Current.LightlessPairRequestNotification); - - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Download Progress Notifications:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - var downloadLocations = GetDownloadNotificationLocations(); - _uiShared.DrawCombo("###enhanced_download", downloadLocations, GetNotificationLocationLabel, (location) => - { - _configService.Current.LightlessDownloadNotification = location; - _configService.Save(); - }, _configService.Current.LightlessDownloadNotification); - - - ImGui.Unindent(); - } - else - { - // Classic notifications when lightless notifs is disabled - var classicLocations = GetClassicNotificationLocations(); - ImGui.Indent(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Info Notifications:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - _uiShared.DrawCombo("###info", classicLocations, GetNotificationLocationLabel, (location) => - { - _configService.Current.InfoNotification = location; - _configService.Save(); - }, _configService.Current.InfoNotification); - - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Warning Notifications:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - _uiShared.DrawCombo("###warning", classicLocations, GetNotificationLocationLabel, (location) => - { - _configService.Current.WarningNotification = location; - _configService.Save(); - }, _configService.Current.WarningNotification); - - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Error Notifications:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - _uiShared.DrawCombo("###error", classicLocations, GetNotificationLocationLabel, (location) => - { - _configService.Current.ErrorNotification = location; - _configService.Save(); - }, _configService.Current.ErrorNotification); - - ImGui.Unindent(); - } - - ImGuiHelpers.ScaledDummy(3); - ImGui.Separator(); - if (useLightlessNotifications) - { - _uiShared.UnderlinedBigText("Test Notifications", UIColors.Get("LightlessBlue")); + UiSharedService.ColorTextWrapped("Choose where each notification type appears.", ImGuiColors.DalamudGrey); ImGuiHelpers.ScaledDummy(5); - - ImGui.Indent(); - - // Test notification buttons - if (_uiShared.IconTextButton(FontAwesomeIcon.Bell, "Test Info")) - { - Mediator.Publish(new NotificationMessage("Test Info", "This is a test info notification with the current settings!", NotificationType.Info)); - } - ImGui.SameLine(); - if (_uiShared.IconTextButton(FontAwesomeIcon.ExclamationTriangle, "Test Warning")) - { - Mediator.Publish(new NotificationMessage("Test Warning", "This is a test warning notification!", NotificationType.Warning)); - } - ImGui.SameLine(); - if (_uiShared.IconTextButton(FontAwesomeIcon.ExclamationCircle, "Test Error")) - { - Mediator.Publish(new NotificationMessage("Test Error", "This is a test error notification!", NotificationType.Error)); - } - _uiShared.DrawHelpText("Preview how notifications will appear with your current settings."); - - ImGui.Unindent(); - - ImGuiHelpers.ScaledDummy(5); - ImGui.Separator(); - _uiShared.UnderlinedBigText("Basic Settings", UIColors.Get("LightlessBlue")); - ImGuiHelpers.ScaledDummy(5); - - int defaultDuration = _configService.Current.DefaultNotificationDurationSeconds; - if (ImGui.SliderInt("Default Duration (seconds)", ref defaultDuration, 3, 60)) - { - _configService.Current.DefaultNotificationDurationSeconds = defaultDuration; - _configService.Save(); - } - _uiShared.DrawHelpText("How long notifications stay visible by default."); - - bool showProgress = _configService.Current.ShowNotificationProgress; - if (ImGui.Checkbox("Show Progress Bars", ref showProgress)) - { - _configService.Current.ShowNotificationProgress = showProgress; - _configService.Save(); - } - _uiShared.DrawHelpText("Display progress bars for download and other progress-based notifications."); - - bool showTimestamp = _configService.Current.ShowNotificationTimestamp; - if (ImGui.Checkbox("Show Timestamps", ref showTimestamp)) - { - _configService.Current.ShowNotificationTimestamp = showTimestamp; - _configService.Save(); - } - _uiShared.DrawHelpText("Display the time when each notification was created."); - - bool autoDismiss = _configService.Current.AutoDismissOnAction; - if (ImGui.Checkbox("Auto-dismiss on Action", ref autoDismiss)) - { - _configService.Current.AutoDismissOnAction = autoDismiss; - _configService.Save(); - } - _uiShared.DrawHelpText("Automatically close notifications when you click an action button."); if (useLightlessNotifications) { - ImGui.Separator(); - _uiShared.UnderlinedBigText("Appearance & Animation", UIColors.Get("LightlessBlue")); - ImGuiHelpers.ScaledDummy(5); - - float opacity = _configService.Current.NotificationOpacity; - if (ImGui.SliderFloat("Notification Opacity", ref opacity, 0.1f, 1.0f, "%.2f")) - { - _configService.Current.NotificationOpacity = opacity; - _configService.Save(); - } - _uiShared.DrawHelpText("Transparency level of notification windows."); - - bool enableAnimations = _configService.Current.EnableNotificationAnimations; - if (ImGui.Checkbox("Enable Animations", ref enableAnimations)) - { - _configService.Current.EnableNotificationAnimations = enableAnimations; - _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(); - } - _uiShared.DrawHelpText("Maximum number of notifications that can be shown at once."); - - bool enableHistory = _configService.Current.EnableNotificationHistory; - if (ImGui.Checkbox("Enable Notification History", ref enableHistory)) - { - _configService.Current.EnableNotificationHistory = enableHistory; - _configService.Save(); - } - _uiShared.DrawHelpText("Keep a history of recent notifications that you can review."); - - if (enableHistory) - { - ImGui.Indent(); - int historySize = _configService.Current.NotificationHistorySize; - if (ImGui.SliderInt("History Size", ref historySize, 10, 200)) - { - _configService.Current.NotificationHistorySize = historySize; - _configService.Save(); - } - _uiShared.DrawHelpText("Number of notifications to keep in history."); - ImGui.Unindent(); - } - } - - ImGui.Separator(); - _uiShared.UnderlinedBigText("Sound Settings", UIColors.Get("LightlessBlue")); - ImGuiHelpers.ScaledDummy(5); - - bool enableSounds = _configService.Current.EnableNotificationSounds; - if (ImGui.Checkbox("Enable Notification Sounds", ref enableSounds)) - { - _configService.Current.EnableNotificationSounds = enableSounds; - _configService.Save(); - } - _uiShared.DrawHelpText("Play FFXIV sound effects when notifications appear."); - - if (enableSounds) - { + // Lightless notification locations ImGui.Indent(); - // float soundVolume = _configService.Current.NotificationSoundVolume; - // if (ImGui.SliderFloat("Sound Volume", ref soundVolume, 0.0f, 1.0f, "%.2f")) - // { - // _configService.Current.NotificationSoundVolume = soundVolume; - // _configService.Save(); - // } - // _uiShared.DrawHelpText("Volume level for notification sounds (Note: FFXIV doesn't support volume control for SE sounds)."); + var lightlessLocations = GetLightlessNotificationLocations(); - // bool useCustomSounds = _configService.Current.UseCustomSounds; - // if (ImGui.Checkbox("Use Custom Sound Effects", ref useCustomSounds)) - // { - // _configService.Current.UseCustomSounds = useCustomSounds; - // _configService.Save(); - // } - // _uiShared.DrawHelpText("Override default sounds with custom FFXIV sound effects."); - DrawSoundCustomization(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Info Notifications:"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + _uiShared.DrawCombo("###enhanced_info", lightlessLocations, GetNotificationLocationLabel, (location) => + { + _configService.Current.LightlessInfoNotification = location; + _configService.Save(); + }, _configService.Current.LightlessInfoNotification); - // if (useCustomSounds) - // { - // ImGui.Indent(); - // DrawSoundCustomization(); - // ImGui.Unindent(); - // } + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Warning Notifications:"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + _uiShared.DrawCombo("###enhanced_warning", lightlessLocations, GetNotificationLocationLabel, + (location) => + { + _configService.Current.LightlessWarningNotification = location; + _configService.Save(); + }, _configService.Current.LightlessWarningNotification); + + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Error Notifications:"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + _uiShared.DrawCombo("###enhanced_error", lightlessLocations, GetNotificationLocationLabel, (location) => + { + _configService.Current.LightlessErrorNotification = location; + _configService.Save(); + }, _configService.Current.LightlessErrorNotification); + + ImGuiHelpers.ScaledDummy(3); + _uiShared.DrawHelpText("Special notification types:"); + + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Pair Request Notifications:"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + _uiShared.DrawCombo("###enhanced_pairrequest", lightlessLocations, GetNotificationLocationLabel, + (location) => + { + _configService.Current.LightlessPairRequestNotification = location; + _configService.Save(); + }, _configService.Current.LightlessPairRequestNotification); + + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Download Progress Notifications:"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + var downloadLocations = GetDownloadNotificationLocations(); + _uiShared.DrawCombo("###enhanced_download", downloadLocations, GetNotificationLocationLabel, + (location) => + { + _configService.Current.LightlessDownloadNotification = location; + _configService.Save(); + }, _configService.Current.LightlessDownloadNotification); + + + ImGui.Unindent(); + } + else + { + // Classic notifications when lightless notifs is disabled + var classicLocations = GetClassicNotificationLocations(); + ImGui.Indent(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Info Notifications:"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + _uiShared.DrawCombo("###info", classicLocations, GetNotificationLocationLabel, (location) => + { + _configService.Current.InfoNotification = location; + _configService.Save(); + }, _configService.Current.InfoNotification); + + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Warning Notifications:"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + _uiShared.DrawCombo("###warning", classicLocations, GetNotificationLocationLabel, (location) => + { + _configService.Current.WarningNotification = location; + _configService.Save(); + }, _configService.Current.WarningNotification); + + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Error Notifications:"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + _uiShared.DrawCombo("###error", classicLocations, GetNotificationLocationLabel, (location) => + { + _configService.Current.ErrorNotification = location; + _configService.Save(); + }, _configService.Current.ErrorNotification); ImGui.Unindent(); } + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + + ImGui.Separator(); + if (useLightlessNotifications) + { + if (_uiShared.MediumTreeNode("Test Notifications", UIColors.Get("LightlessPurple"))) + { + ImGui.Indent(); + + // Test notification buttons + if (_uiShared.IconTextButton(FontAwesomeIcon.Bell, "Test Info")) + { + Mediator.Publish(new NotificationMessage("Test Info", + "This is a test info notification to let you know Chocola is cute :3", NotificationType.Info)); + } + + ImGui.SameLine(); + if (_uiShared.IconTextButton(FontAwesomeIcon.ExclamationTriangle, "Test Warning")) + { + Mediator.Publish(new NotificationMessage("Test Warning", "This is a test warning notification!", + NotificationType.Warning)); + } + + ImGui.SameLine(); + if (_uiShared.IconTextButton(FontAwesomeIcon.ExclamationCircle, "Test Error")) + { + Mediator.Publish(new NotificationMessage("Test Error", "This is a test error notification!", + NotificationType.Error)); + } + + ImGuiHelpers.ScaledDummy(3); + if (_uiShared.IconTextButton(FontAwesomeIcon.UserPlus, "Test Pair Request")) + { + _lightlessNotificationService.ShowPairRequestNotification( + "Test User", + "test-uid-123", + () => + { + Mediator.Publish(new NotificationMessage("Accepted", "You accepted the test pair request.", + NotificationType.Info)); + }, + () => + { + Mediator.Publish(new NotificationMessage("Declined", "You declined the test pair request.", + NotificationType.Info)); + } + ); + } + + ImGui.SameLine(); + if (_uiShared.IconTextButton(FontAwesomeIcon.Download, "Test Download Progress")) + { + _lightlessNotificationService.ShowPairDownloadNotification( + new List<(string playerName, float progress, string status)> + { + ("Player One", 0.35f, "downloading"), + ("Player Two", 0.75f, "downloading"), + ("Player Three", 1.0f, "downloading") + }, + queueWaiting: 2 + ); + } + + _uiShared.DrawHelpText("Preview how notifications will appear with your current settings."); + + ImGui.Unindent(); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + + 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)) + { + _configService.Current.DefaultNotificationDurationSeconds = defaultDuration; + _configService.Save(); + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.DefaultNotificationDurationSeconds = 10; + _configService.Save(); + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (10 seconds)."); + + _uiShared.DrawHelpText("How long notifications stay visible by default."); + + bool showTimestamp = _configService.Current.ShowNotificationTimestamp; + if (ImGui.Checkbox("Show Timestamps", ref showTimestamp)) + { + _configService.Current.ShowNotificationTimestamp = showTimestamp; + _configService.Save(); + } + + _uiShared.DrawHelpText("Display the time when each notification was created."); + + bool dismissOnClick = _configService.Current.DismissNotificationOnClick; + if (ImGui.Checkbox("Dismiss on Click", ref dismissOnClick)) + { + _configService.Current.DismissNotificationOnClick = dismissOnClick; + _configService.Save(); + } + + _uiShared.DrawHelpText("Click anywhere on a notification to dismiss it. Notifications with action buttons (like pair requests) are excluded."); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + + if (useLightlessNotifications) + { + ImGui.Separator(); + if (_uiShared.MediumTreeNode("Appearance & Animation", UIColors.Get("LightlessPurple"))) + { + + float opacity = _configService.Current.NotificationOpacity; + if (ImGui.SliderFloat("Notification Opacity", ref opacity, 0.1f, 1.0f, "%.2f")) + { + _configService.Current.NotificationOpacity = opacity; + _configService.Save(); + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.NotificationOpacity = 0.95f; + _configService.Save(); + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (0.95)."); + + _uiShared.DrawHelpText("Transparency level of notification windows."); + + bool enableAnimations = _configService.Current.EnableNotificationAnimations; + if (ImGui.Checkbox("Enable Animations", ref enableAnimations)) + { + _configService.Current.EnableNotificationAnimations = enableAnimations; + _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.Save(); + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (5)."); + + _uiShared.DrawHelpText("Maximum number of notifications that can be shown at once."); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + } + + ImGui.Separator(); + if (_uiShared.MediumTreeNode("Sound Settings", UIColors.Get("LightlessPurple"))) + { + ImGui.TextUnformatted("Notification Sounds"); + ImGuiHelpers.ScaledDummy(5); + + DrawSoundTable(); + + _uiShared.DrawHelpText( + "Configure which sounds play for each notification type. Use the play button to preview sounds."); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } ImGui.Separator(); _uiShared.UnderlinedBigText("Specific Notification Types", UIColors.Get("LightlessBlue")); ImGuiHelpers.ScaledDummy(5); - UiSharedService.ColorTextWrapped("Configure specific types of notifications and their behavior.", ImGuiColors.DalamudGrey); + UiSharedService.ColorTextWrapped("Configure specific types of notifications and their behavior.", + ImGuiColors.DalamudGrey); ImGuiHelpers.ScaledDummy(3); // Online Notifications Section @@ -2920,7 +3272,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ShowOnlineNotifications = onlineNotifs; _configService.Save(); } - _uiShared.DrawHelpText("Show notifications when pairs come online. These will use the Info notification location settings above."); + + _uiShared.DrawHelpText( + "Show notifications when pairs come online. These will use the Info notification location settings above."); using var disabled = ImRaii.Disabled(!onlineNotifs); ImGui.Indent(); @@ -2929,6 +3283,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ShowOnlineNotificationsOnlyForIndividualPairs = onlineNotifsPairsOnly; _configService.Save(); } + _uiShared.DrawHelpText("Only show online notifications for individual pairs (not syncshell members)."); if (ImGui.Checkbox("Notify only for named pairs", ref onlineNotifsNamedOnly)) @@ -2936,7 +3291,9 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ShowOnlineNotificationsOnlyForNamedPairs = onlineNotifsNamedOnly; _configService.Save(); } - _uiShared.DrawHelpText("Only show online notifications for pairs where you have set an individual note."); + + _uiShared.DrawHelpText( + "Only show online notifications for pairs where you have set an individual note."); ImGui.Unindent(); _uiShared.ColoredSeparator(UIColors.Get("LightlessGreen"), 1.5f); @@ -2946,17 +3303,15 @@ public class SettingsUi : WindowMediatorSubscriberBase // Pairing Request Notifications Section if (_uiShared.MediumTreeNode("Pairing Request Notifications", UIColors.Get("LightlessBlue"))) { - UiSharedService.ColorTextWrapped("Pairing requests always show as interactive notifications with Accept/Decline buttons. These settings control additional behavior.", ImGuiColors.DalamudGrey); + UiSharedService.ColorTextWrapped( + "Pairing requests always show as interactive notifications with Accept/Decline buttons. Configure the sound in the Sound Settings table above.", + ImGuiColors.DalamudGrey); ImGuiHelpers.ScaledDummy(3); - // Note: Pairing requests are always shown as interactive notifications - // This section can be expanded later with additional pairing notification settings - _uiShared.ColoredSeparator(UIColors.Get("LightlessBlue"), 1.5f); ImGui.TreePop(); } - // System Notifications Section if (_uiShared.MediumTreeNode("System Notifications", UIColors.Get("LightlessYellow"))) { var disableOptionalPluginWarnings = _configService.Current.DisableOptionalPluginWarnings; @@ -2965,6 +3320,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.DisableOptionalPluginWarnings = disableOptionalPluginWarnings; _configService.Save(); } + _uiShared.DrawHelpText("Disable warning notifications for missing optional plugins."); _uiShared.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.5f); @@ -2972,8 +3328,7 @@ public class SettingsUi : WindowMediatorSubscriberBase } ImGui.Separator(); - _uiShared.UnderlinedBigText("Location Descriptions", UIColors.Get("LightlessBlue")); - ImGuiHelpers.ScaledDummy(5); + // Location descriptions removed - information is now inline with each setting } } @@ -2981,9 +3336,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { return new[] { - NotificationLocation.LightlessUi, - NotificationLocation.ChatAndLightlessUi, - NotificationLocation.Nowhere + NotificationLocation.LightlessUi, NotificationLocation.ChatAndLightlessUi, NotificationLocation.Nowhere }; } @@ -2991,10 +3344,8 @@ public class SettingsUi : WindowMediatorSubscriberBase { return new[] { - NotificationLocation.LightlessUi, - NotificationLocation.ChatAndLightlessUi, - NotificationLocation.TextOverlay, - NotificationLocation.Nowhere + NotificationLocation.LightlessUi, NotificationLocation.ChatAndLightlessUi, + NotificationLocation.TextOverlay, NotificationLocation.Nowhere }; } @@ -3002,9 +3353,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { return new[] { - NotificationLocation.Toast, - NotificationLocation.Chat, - NotificationLocation.Both, + NotificationLocation.Toast, NotificationLocation.Chat, NotificationLocation.Both, NotificationLocation.Nowhere }; } @@ -3024,137 +3373,144 @@ public class SettingsUi : WindowMediatorSubscriberBase }; } - private void DrawSoundCustomization() + private void DrawSoundTable() { 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") + (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") }; - // Info Sound - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Info Sound:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - var currentInfoSound = _configService.Current.CustomInfoSoundId; - var currentInfoIndex = Array.FindIndex(soundEffects, s => s.Item1 == currentInfoSound); - if (currentInfoIndex == -1) currentInfoIndex = 1; // Default to Se2 + if (ImGui.BeginTable("##SoundTable", 3, + ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) + { + 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.TableHeadersRow(); - if (ImGui.Combo("###info_sound", ref currentInfoIndex, soundEffects.Select(s => s.Item2).ToArray(), soundEffects.Length)) - { - _configService.Current.CustomInfoSoundId = soundEffects[currentInfoIndex].Item1; - _configService.Save(); - } - ImGui.SameLine(); - ImGui.PushID("test_info"); - if (_uiShared.IconButton(FontAwesomeIcon.Play)) - { - try + var soundTypes = new[] { - FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.PlayChatSoundEffect(_configService.Current.CustomInfoSoundId); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to play test sound"); - } - } - ImGui.PopID(); - UiSharedService.AttachToolTip("Test this sound"); + ("Info", 0, _configService.Current.CustomInfoSoundId, _configService.Current.DisableInfoSound, 2u), + ("Warning", 1, _configService.Current.CustomWarningSoundId, _configService.Current.DisableWarningSound, 15u), + ("Error", 2, _configService.Current.CustomErrorSoundId, _configService.Current.DisableErrorSound, 16u), + ("Pair Request", 3, _configService.Current.PairRequestSoundId, _configService.Current.DisablePairRequestSound, 5u) + }; - // Warning Sound - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Warning Sound:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - var currentWarningSound = _configService.Current.CustomWarningSoundId; - var currentWarningIndex = Array.FindIndex(soundEffects, s => s.Item1 == currentWarningSound); - if (currentWarningIndex == -1) currentWarningIndex = 14; // Default to Se15 + foreach (var (typeName, typeIndex, currentSoundId, isDisabled, defaultSoundId) in soundTypes) + { + ImGui.TableNextRow(); - if (ImGui.Combo("###warning_sound", ref currentWarningIndex, soundEffects.Select(s => s.Item2).ToArray(), soundEffects.Length)) - { - _configService.Current.CustomWarningSoundId = soundEffects[currentWarningIndex].Item1; - _configService.Save(); - } - ImGui.SameLine(); - ImGui.PushID("test_warning"); - if (_uiShared.IconButton(FontAwesomeIcon.Play)) - { - try - { - FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.PlayChatSoundEffect(_configService.Current.CustomWarningSoundId); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to play test sound"); - } - } - ImGui.PopID(); - UiSharedService.AttachToolTip("Test this sound"); + // Type column + ImGui.TableSetColumnIndex(0); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(typeName); - // Error Sound - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Error Sound:"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - var currentErrorSound = _configService.Current.CustomErrorSoundId; - var currentErrorIndex = Array.FindIndex(soundEffects, s => s.Item1 == currentErrorSound); - if (currentErrorIndex == -1) currentErrorIndex = 15; // Default to Se16 - - if (ImGui.Combo("###error_sound", ref currentErrorIndex, soundEffects.Select(s => s.Item2).ToArray(), soundEffects.Length)) - { - _configService.Current.CustomErrorSoundId = soundEffects[currentErrorIndex].Item1; - _configService.Save(); - } - ImGui.SameLine(); - ImGui.PushID("test_error"); - if (_uiShared.IconButton(FontAwesomeIcon.Play)) - { - try - { - FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.PlayChatSoundEffect(_configService.Current.CustomErrorSoundId); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to play test sound"); - } - } - ImGui.PopID(); - UiSharedService.AttachToolTip("Test this sound"); - - ImGuiHelpers.ScaledDummy(5); - if (_uiShared.IconTextButton(FontAwesomeIcon.VolumeUp, "Test All Sounds")) - { - Task.Run(async () => - { - try + // Sound picker column + ImGui.TableSetColumnIndex(1); + using (ImRaii.Disabled(isDisabled)) { - FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.PlayChatSoundEffect(_configService.Current.CustomInfoSoundId); - await Task.Delay(800); - FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.PlayChatSoundEffect(_configService.Current.CustomWarningSoundId); - await Task.Delay(800); - FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.PlayChatSoundEffect(_configService.Current.CustomErrorSoundId); + var currentIndex = Array.FindIndex(soundEffects, s => s.Item1 == currentSoundId); + if (currentIndex == -1) currentIndex = 1; + + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + if (ImGui.Combo($"##sound_{typeIndex}", ref currentIndex, + soundEffects.Select(s => s.Item2).ToArray(), soundEffects.Length)) + { + var newSoundId = soundEffects[currentIndex].Item1; + switch (typeIndex) + { + case 0: _configService.Current.CustomInfoSoundId = newSoundId; break; + case 1: _configService.Current.CustomWarningSoundId = newSoundId; break; + case 2: _configService.Current.CustomErrorSoundId = newSoundId; break; + case 3: _configService.Current.PairRequestSoundId = 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"); } - catch (Exception ex) + + // Actions column + ImGui.TableSetColumnIndex(2); + var availableWidth = ImGui.GetContentRegionAvail().X; + var buttonWidth = (availableWidth - ImGui.GetStyle().ItemSpacing.X) / 2; + + // Reset button + using var resetId = ImRaii.PushId($"Reset_{typeIndex}"); + bool isDefault = currentSoundId == defaultSoundId; + + using (ImRaii.Disabled(isDefault)) { - _logger.LogWarning(ex, "Failed to play test sounds"); + 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; + } + _configService.Save(); + } + } } - }); + UiSharedService.AttachToolTip(isDefault ? "Sound is already at default value" : "Reset to default sound"); + + // Disable toggle button + ImGui.SameLine(); + using var disableId = ImRaii.PushId($"Disable_{typeIndex}"); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var icon = isDisabled ? FontAwesomeIcon.VolumeOff : FontAwesomeIcon.VolumeUp; + var color = isDisabled ? UIColors.Get("DimRed") : UIColors.Get("LightlessGreen"); + + ImGui.PushStyleColor(ImGuiCol.Button, color); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, color * new Vector4(1.2f, 1.2f, 1.2f, 1f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, color * new Vector4(0.8f, 0.8f, 0.8f, 1f)); + + if (ImGui.Button(icon.ToIconString(), new Vector2(buttonWidth, 0))) + { + bool newDisabled = !isDisabled; + switch (typeIndex) + { + case 0: _configService.Current.DisableInfoSound = newDisabled; break; + case 1: _configService.Current.DisableWarningSound = newDisabled; break; + case 2: _configService.Current.DisableErrorSound = newDisabled; break; + case 3: _configService.Current.DisablePairRequestSound = newDisabled; break; + } + _configService.Save(); + } + + ImGui.PopStyleColor(3); + } + UiSharedService.AttachToolTip(isDisabled ? "Sound is disabled - click to enable" : "Sound is enabled - click to disable"); + } + + ImGui.EndTable(); } - _uiShared.DrawHelpText("Play all custom sounds in sequence: Info → Warning → Error"); } } + +