diff --git a/HellionChat/Ui/Settings.cs b/HellionChat/Ui/Settings.cs index 5ce19f8..b1b3c2d 100755 --- a/HellionChat/Ui/Settings.cs +++ b/HellionChat/Ui/Settings.cs @@ -1,314 +1,46 @@ using System.Numerics; using Dalamud.Bindings.ImGui; -using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface; using Dalamud.Interface.Windowing; using Dalamud.Utility; using HellionChat.Resources; -using HellionChat.Ui.SettingsTabs; -using HellionChat.Util; using Microsoft.Extensions.Logging; namespace HellionChat.Ui; -internal enum SettingsView +// Placeholder window kept alive so the slash-command paths, UiBuilder +// open-handlers and the WindowSystem registration stay functional until +// the new settings UI lands in a later cycle. The body just points users +// at the JSON config and at /hellion reset for theme recovery. +public sealed class SettingsWindow : Window { - Overview, - Detail, -} - -public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window -{ - internal readonly Plugin Plugin; - - private Configuration Mutable { get; } - private List Tabs { get; } - private int CurrentTab; - private SettingsView View = SettingsView.Overview; - - // Set when a section is freshly entered; the first Draw afterwards reads it - // and clears it, so each section starts collapsed every time it is opened. - private bool _sectionJustEntered; - private readonly SettingsOverview Overview; + private readonly Plugin _plugin; internal SettingsWindow(Plugin plugin, ILoggerFactory loggerFactory) : base($"{Language.Settings_Title.Format(Plugin.PluginName)}###chat2-settings") { - Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse; - + _plugin = plugin; + _ = loggerFactory; SizeCondition = ImGuiCond.FirstUseEver; SizeConstraints = new WindowSizeConstraints { - MinimumSize = new Vector2(475, 600), + MinimumSize = new Vector2(400, 200), MaximumSize = new Vector2(float.MaxValue, float.MaxValue), }; - - Plugin = plugin; - Mutable = new Configuration(); - - Overview = new SettingsOverview(this); - - Tabs = - [ - new General(Plugin, Mutable), - new Appearance(Plugin, Mutable, loggerFactory.CreateLogger()), - new Chat(Plugin, Mutable), - new SettingsTabs.Window(Plugin, Mutable), - new SettingsTabs.Tabs(Plugin, Mutable), - new DataAndPrivacy(Plugin, Mutable, loggerFactory.CreateLogger()), - new About(Plugin, Mutable), - ]; - RespectCloseHotkey = false; DisableWindowSounds = true; - - Initialise(); - } - - public void Dispose() - { - // Slash-command + OpenConfigUi tear-down moved to Plugin.TearDownCommands. - } - - private void Initialise() - { - Mutable.UpdateFrom(Plugin.Config, false); } public override void Draw() { - if (ImGui.IsWindowAppearing()) - { - Initialise(); - View = SettingsView.Overview; - } - - // ESC in Detail view returns to Overview. Window focus check is - // required so ESC doesn't fire when the user targets a different window. - if ( - View == SettingsView.Detail - && ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows) - && ImGui.IsKeyPressed(ImGuiKey.Escape) - ) - { - View = SettingsView.Overview; - return; - } - - if (View == SettingsView.Overview) - Overview.Draw(); - else - DrawDetail(); - - ImGui.Separator(); - DrawSaveButtons(); - } - - internal void OpenSection(int tabIndex) - { - CurrentTab = tabIndex; - View = SettingsView.Detail; - _sectionJustEntered = true; - } - - internal void OpenOverview() - { - View = SettingsView.Overview; - } - - private void DrawDetail() - { - // Breadcrumb header -- accent cyan, clickable, returns to Overview. - using (ImRaii.PushColor(ImGuiCol.Text, 0xFF00BED2u)) - using (ImRaii.PushColor(ImGuiCol.Button, 0u)) - using (ImRaii.PushColor(ImGuiCol.ButtonHovered, 0x33FFFFFFu)) - using (ImRaii.PushColor(ImGuiCol.ButtonActive, 0x55FFFFFFu)) - { - if (ImGui.SmallButton("<- Settings")) - { - View = SettingsView.Overview; - return; - } - } + using (_plugin.FontManager.FontAwesome.Push()) + ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString()); ImGui.SameLine(); - ImGui.TextUnformatted("·"); - ImGui.SameLine(); - ImGui.TextUnformatted(Tabs[CurrentTab].Name.Split("###")[0]); - + ImGui.TextUnformatted("Settings UI lands in a later cycle."); ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - // Section content fills full width. Navigation back to another - // section goes via the breadcrumb or ESC. - var style = ImGui.GetStyle(); - var height = - ImGui.GetContentRegionAvail().Y - - style.FramePadding.Y * 2 - - style.ItemSpacing.Y - - style.ItemInnerSpacing.Y * 2 - - ImGui.CalcTextSize("A").Y; - - using var child = ImRaii.Child("##chat2-settings-detail", new Vector2(-1, height)); - if (child.Success) - { - Tabs[CurrentTab].Draw(_sectionJustEntered); - _sectionJustEntered = false; - } - } - - private void DrawSaveButtons() - { - var save = ImGui.Button(Language.Settings_Save); - - ImGui.SameLine(); - - if (ImGui.Button(Language.Settings_SaveAndClose)) - { - save = true; - IsOpen = false; - } - - ImGui.SameLine(); - - if (ImGui.Button(Language.Settings_Discard)) - IsOpen = false; - - const string buttonLabel = "Anna's Ko-fi"; - const string buttonLabel2 = "Infi's Ko-fi"; - - using (ImRaii.PushColor(ImGuiCol.Button, ColourUtil.RgbaToAbgr(0xFF5E5BFF))) - using (ImRaii.PushColor(ImGuiCol.ButtonHovered, ColourUtil.RgbaToAbgr(0xFF7775FF))) - using (ImRaii.PushColor(ImGuiCol.ButtonActive, ColourUtil.RgbaToAbgr(0xFF4542FF))) - using (ImRaii.PushColor(ImGuiCol.Text, 0xFFFFFFFF)) - { - var buttonWidth = - ImGui.CalcTextSize(buttonLabel).X + ImGui.GetStyle().FramePadding.X * 2; - var buttonWidth2 = - ImGui.CalcTextSize(buttonLabel2).X + ImGui.GetStyle().FramePadding.X * 2; - ImGui.SameLine( - ImGui.GetContentRegionAvail().X - - buttonWidth - - buttonWidth2 - - ImGui.GetStyle().ItemSpacing.X - ); - - if (ImGui.Button(buttonLabel2)) - Plugin.PlatformUtil.OpenLink("https://ko-fi.com/infiii"); - - ImGui.SameLine(); - - if (ImGui.Button(buttonLabel)) - Plugin.PlatformUtil.OpenLink("https://ko-fi.com/lojewalo"); - } - - if (!save) - return; - - var hideChanged = !Mutable.HideChat && Mutable.HideChat != Plugin.Config.HideChat; - var languageChanged = Mutable.LanguageOverride != Plugin.Config.LanguageOverride; - - // v1.5.3: Auto-enable the ExtraGlyphRanges flag matching the new - // locale so non-Latin scripts render immediately. Without this, - // a user switching to Korean would see "===" until they manually - // tick the Korean range in Fonts & Colours. - if (languageChanged) - { - var required = Mutable.LanguageOverride.RequiredGlyphRanges(); - if (required != 0) - Mutable.ExtraGlyphRanges |= required; - } - - var fontChanged = - Mutable.GlobalFontV2 != Plugin.Config.GlobalFontV2 - || Mutable.JapaneseFontV2 != Plugin.Config.JapaneseFontV2 - || Mutable.ItalicFontV2 != Plugin.Config.ItalicFontV2 - || Mutable.ExtraGlyphRanges != Plugin.Config.ExtraGlyphRanges - || Mutable.UseHellionFont != Plugin.Config.UseHellionFont; - var fontSizeChanged = - Math.Abs(Mutable.SymbolsFontSizeV2 - Plugin.Config.SymbolsFontSizeV2) > 0.001 - || Math.Abs(Mutable.FontSizeV2 - Plugin.Config.FontSizeV2) > 0.001; - var italicStateChanged = Mutable.ItalicEnabled != Plugin.Config.ItalicEnabled; - - // Only refilter when filter-relevant settings changed. Clear+Refilter - // reloads from the DB and silently drops in-session messages that - // weren't persisted (Privacy-First blocks most channels). Cosmetic - // changes (theme, icons, layout) skip the cycle. - var filtersChanged = HasFilterRelevantChanges(); - - Plugin.Config.UpdateFrom(Mutable, true); - - // Defer save by 60 frames to avoid committing changes that cause a crash. - Plugin.DeferredSaveFrames = 60; - if (filtersChanged) - { - Plugin.MessageManager.ClearAllTabs(); - Plugin.MessageManager.FilterAllTabsAsync(); - } - - if (fontChanged || fontSizeChanged || italicStateChanged) - Plugin.FontManager.RebuildDelegateFonts(); - - if (languageChanged) - Plugin.LanguageChanged(Plugin.Interface.UiLanguage); - - if (hideChanged) - GameFunctions.GameFunctions.SetChatInteractable(true); - - if (Plugin.Config.ShowEmotes) - _ = EmoteCache.LoadData(); - - Initialise(); - } - - // Returns true if any filter-relevant setting changed between Plugin.Config - // and the Mutable copy. Gates Clear+Refilter on Save so cosmetic changes - // don't wipe in-session chat history. - private bool HasFilterRelevantChanges() - { - if (Mutable.PrivacyFilterEnabled != Plugin.Config.PrivacyFilterEnabled) - return true; - if (Mutable.PrivacyPersistUnknownChannels != Plugin.Config.PrivacyPersistUnknownChannels) - return true; - if (!Mutable.PrivacyPersistChannels.SetEquals(Plugin.Config.PrivacyPersistChannels)) - return true; - - // FilterIncludePreviousSessions changes the GetMostRecentMessages - // window and is filter-relevant even outside the Privacy block. - if (Mutable.FilterIncludePreviousSessions != Plugin.Config.FilterIncludePreviousSessions) - return true; - - // Compare persistent tabs only -- TempTabs are never refiltered. - var origPersistent = Plugin.Config.Tabs.Where(t => !t.IsTempTab).ToList(); - var newPersistent = Mutable.Tabs.Where(t => !t.IsTempTab).ToList(); - - if (origPersistent.Count != newPersistent.Count) - return true; - - for (var i = 0; i < origPersistent.Count; i++) - { - var orig = origPersistent[i]; - var neu = newPersistent[i]; - - // Identifier mismatch means reorder or slot swap -- treat as filter-relevant. - if (orig.Identifier != neu.Identifier) - return true; - - if (orig.ExtraChatAll != neu.ExtraChatAll) - return true; - if (!orig.ExtraChatChannels.SetEquals(neu.ExtraChatChannels)) - return true; - - if (orig.SelectedChannels.Count != neu.SelectedChannels.Count) - return true; - foreach (var pair in orig.SelectedChannels) - { - if (!neu.SelectedChannels.TryGetValue(pair.Key, out var nv)) - return true; - if (!pair.Value.Equals(nv)) - return true; - } - } - - return false; + ImGui.TextWrapped( + "For now, edit the plugin config JSON directly. Use /hellion reset to " + + "drop a broken custom theme out of the loader cache without touching the file on disk." + ); } } diff --git a/HellionChat/Ui/SettingsOverview.cs b/HellionChat/Ui/SettingsOverview.cs deleted file mode 100644 index 88fda98..0000000 --- a/HellionChat/Ui/SettingsOverview.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Numerics; -using Dalamud.Bindings.ImGui; -using Dalamud.Interface; -using Dalamud.Interface.Utility.Raii; -using HellionChat.Resources; -using HellionChat.Util; - -namespace HellionChat.Ui; - -internal sealed class SettingsOverview -{ - private readonly SettingsWindow _window; - - // Card order matches the Tabs index in SettingsWindow 1:1. - private static (FontAwesomeIcon Icon, string Title, string Subtext)[] BuildCardDefs() => - [ - ( - FontAwesomeIcon.SlidersH, - HellionStrings.Settings_Card_General_Title, - HellionStrings.Settings_Card_General_Subtext - ), - ( - FontAwesomeIcon.Palette, - HellionStrings.Settings_Card_Appearance_Title, - HellionStrings.Settings_Card_Appearance_Subtext - ), - ( - FontAwesomeIcon.Comments, - HellionStrings.Settings_Card_Chat_Title, - HellionStrings.Settings_Card_Chat_Subtext - ), - ( - FontAwesomeIcon.WindowMaximize, - HellionStrings.Settings_Card_Window_Title, - HellionStrings.Settings_Card_Window_Subtext - ), - ( - FontAwesomeIcon.FolderTree, - HellionStrings.Settings_Card_Tabs_Title, - HellionStrings.Settings_Card_Tabs_Subtext - ), - ( - FontAwesomeIcon.Database, - HellionStrings.Settings_Card_DataManagement_Title, - HellionStrings.Settings_Card_DataManagement_Subtext - ), - ( - FontAwesomeIcon.InfoCircle, - HellionStrings.Settings_Card_Information_Title, - HellionStrings.Settings_Card_Information_Subtext - ), - ]; - - public SettingsOverview(SettingsWindow window) - { - _window = window; - } - - public void Draw() - { - var avail = ImGui.GetContentRegionAvail(); - var columns = avail.X >= 700f ? 3 : 2; - var cardWidth = (avail.X - (columns - 1) * 8f) / columns; - // 110f accommodates two-line subtexts; wrap width is matched in DrawCard. - var cardHeight = 110f; - - // One draw-list lookup per frame instead of one per card. - var drawList = ImGui.GetWindowDrawList(); - var cardDefs = BuildCardDefs(); - for (var i = 0; i < cardDefs.Length; i++) - { - var (icon, title, subtext) = cardDefs[i]; - DrawCard(i, icon, title, subtext, cardWidth, cardHeight, drawList); - - if ((i + 1) % columns != 0 && i != cardDefs.Length - 1) - ImGui.SameLine(); - } - } - - private void DrawCard( - int index, - FontAwesomeIcon icon, - string title, - string subtext, - float w, - float h, - ImDrawListPtr drawList - ) - { - // BeginGroup makes the card a single layout item so SameLine works - // in the caller loop -- without it ImGui tracks each child separately. - ImGui.BeginGroup(); - - var cursorBefore = ImGui.GetCursorScreenPos(); - var clicked = ImGui.InvisibleButton($"##settings-card-{index}", new Vector2(w, h)); - var hovered = ImGui.IsItemHovered(); - var bgColor = hovered ? 0xFF22303Fu : 0xFF1A2538u; - - drawList.AddRectFilled(cursorBefore, cursorBefore + new Vector2(w, h), bgColor, 4f); - - var iconPos = cursorBefore + new Vector2(16f, 12f); - var titlePos = cursorBefore + new Vector2(16f, 40f); - var subtextPos = cursorBefore + new Vector2(16f, 62f); - - var titleColor = ColourUtil.RgbaToAbgr(0xE6F4F1FFu); - var subtextColor = ColourUtil.RgbaToAbgr(0x8FA3B5FFu); - - using (_window.Plugin.FontManager.FontAwesome.Push()) - { - drawList.AddText(iconPos, titleColor, icon.ToIconString()); - } - - drawList.AddText(titlePos, titleColor, title); - - // Subtext wraps at card inner width (16px padding each side) via DrawList - // to avoid expanding the group bounds and breaking SameLine in the card row. - var subtextWrapWidth = w - 32f; - drawList.AddText( - ImGui.GetFont(), - ImGui.GetFontSize(), - subtextPos, - subtextColor, - subtext, - subtextWrapWidth - ); - - ImGui.EndGroup(); - - if (clicked) - _window.OpenSection(index); - } -} diff --git a/HellionChat/Ui/SettingsTabs/About.cs b/HellionChat/Ui/SettingsTabs/About.cs deleted file mode 100644 index c20581a..0000000 --- a/HellionChat/Ui/SettingsTabs/About.cs +++ /dev/null @@ -1,493 +0,0 @@ -using System.Numerics; -using Dalamud.Bindings.ImGui; -using Dalamud.Interface; -using Dalamud.Interface.Colors; -using Dalamud.Interface.Utility; -using Dalamud.Interface.Utility.Raii; -using HellionChat.Branding; -using HellionChat.Integrations; -using HellionChat.Resources; -using HellionChat.Util; - -namespace HellionChat.Ui.SettingsTabs; - -// The About tab absorbs the former Integrations tab (now the first section) -// and organises its remaining content into four thematic sections. -internal sealed class About : ISettingsTab -{ - private Plugin Plugin { get; } - private Configuration Mutable { get; } - - public string Name => HellionStrings.Settings_Tab_Information + "###tabs-information"; - - private readonly List Translators = - [ - "q673135110", - "Akizem", - "d0tiKs", - "Moonlight_Everlit", - "Dark32", - "andreycout", - "Button_", - "Cali666", - "cassandra308", - "lokinmodar", - "jtabox", - "AkiraYorumoto", - "MKhayle", - "elena.space", - "imlisa", - "andrei5125", - "ShivaMaheshvara", - "aislinn87", - "nishinatsu051", - "lichuyuan", - "Risu64", - "yummypillow", - "witchymary", - "Yuzumi", - "zomsakura", - "Sirayuki", - ]; - - internal About(Plugin plugin, Configuration mutable) - { - Plugin = plugin; - Mutable = mutable; - Translators.Sort( - (a, b) => - string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal) - ); - } - - public void Draw(bool sectionJustEntered) - { - using var wrap = ImRaii.TextWrapPos(0.0f); - - DrawExtensionsSection(sectionJustEntered); - ImGui.Spacing(); - DrawPluginInfoSection(sectionJustEntered); - ImGui.Spacing(); - DrawProjectSection(sectionJustEntered); - ImGui.Spacing(); - DrawTranslatorsSection(sectionJustEntered); - ImGui.Spacing(); - DrawChangelogSection(sectionJustEntered); - } - - // ── Extensions ────────────────────────────────────────────────────────── - - private void DrawExtensionsSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Extensions); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.TextWrapped(HellionStrings.Settings_Integrations_Intro); - ImGui.Spacing(); - ImGui.Spacing(); - - DrawHonorificSection(); - ImGui.Spacing(); - ImGui.Spacing(); - - DrawComingSoonSection(); - ImGui.Spacing(); - ImGui.Spacing(); - - DrawGotAnIdeaSection(); - } - } - - private void DrawHonorificSection() - { - DrawSectionHeader(HellionStrings.Settings_Integrations_Honorific_SectionHeader); - - DrawHonorificStatus(); - ImGui.Spacing(); - - // Toggle works regardless of detection state: "show when available, - // hide otherwise". Disabling it when Honorific is missing would force - // the user to retoggle on every reload. - if ( - ImGui.Checkbox( - HellionStrings.Settings_Integrations_Honorific_Toggle, - ref Mutable.ShowHonorificTitleInHeader - ) - ) - { - Plugin.SaveConfig(); - } - - using (ImRaii.PushIndent()) - { - using ( - ImRaii.PushColor( - ImGuiCol.Text, - ColourUtil.RgbaToAbgr(Plugin.ThemeRegistry.Active.Colors.TextMuted) - ) - ) - { - ImGui.TextWrapped(HellionStrings.Settings_Integrations_Honorific_ToggleHint); - } - - if ( - ImGui.Checkbox( - HellionStrings.Settings_Integrations_Honorific_Glow_Toggle, - ref Mutable.ShowHonorificGlow - ) - ) - { - Plugin.SaveConfig(); - } - ImGuiUtil.HelpMarker(HellionStrings.Settings_Integrations_Honorific_Glow_Hint); - } - - // Honorific has no LICENSE in its repo so we link upstream and author - // instead of bundling assets. Text labels because FA Brands isn't - // guaranteed in Dalamud's font set. - ImGui.Spacing(); - if (ImGui.Button(HellionStrings.Settings_Integrations_Honorific_LinkRepo)) - { - Plugin.PlatformUtil.OpenLink(IntegrationLinks.HonorificRepo); - } - ImGui.SameLine(); - if (ImGui.Button(HellionStrings.Settings_Integrations_Honorific_LinkAuthor)) - { - Plugin.PlatformUtil.OpenLink(IntegrationLinks.HonorificAuthor); - } - } - - private void DrawHonorificStatus() - { - var theme = Plugin.ThemeRegistry.Active; - var service = Plugin.HonorificService; - - if (service.IsAvailable && service.DetectedApiVersion is { } version) - { - DrawStatusGlyph('●', theme.Colors.StatusSuccess); - ImGui.SameLine(); - ImGui.TextUnformatted( - string.Format( - HellionStrings.Settings_Integrations_Honorific_Status_Detected, - version.Major, - version.Minor - ) - ); - } - else if (service.DetectedApiVersion is { } incompatibleVersion) - { - DrawStatusGlyph('⚠', theme.Colors.StatusWarning); - ImGui.SameLine(); - ImGui.TextUnformatted( - string.Format( - HellionStrings.Settings_Integrations_Honorific_Status_Incompatible, - HonorificService.ExpectedApiMajor, - incompatibleVersion.Major, - incompatibleVersion.Minor - ) - ); - } - else - { - DrawStatusGlyph('○', theme.Colors.TextMuted); - ImGui.SameLine(); - ImGui.TextUnformatted( - HellionStrings.Settings_Integrations_Honorific_Status_NotInstalled - ); - } - } - - private static void DrawStatusGlyph(char glyph, uint rgba) - { - using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(rgba))) - { - ImGui.TextUnformatted(glyph.ToString()); - } - } - - private void DrawComingSoonSection() - { - DrawSectionHeader(HellionStrings.Settings_Integrations_ComingSoon_SectionHeader); - ImGui.TextWrapped(HellionStrings.Settings_Integrations_ComingSoon_Intro); - ImGui.Spacing(); - - // Each integration cycle removes its stub here and adds a full section above. - DrawComingSoonItem( - HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Title, - HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Description - ); - DrawComingSoonItem( - HellionStrings.Settings_Integrations_ComingSoon_Notifications_Title, - HellionStrings.Settings_Integrations_ComingSoon_Notifications_Description - ); - DrawComingSoonItem( - HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Title, - HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Description - ); - DrawComingSoonItem( - HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Title, - HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Description - ); - DrawComingSoonItem( - HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Title, - HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Description - ); - } - - private void DrawComingSoonItem(string title, string description) - { - var theme = Plugin.ThemeRegistry.Active; - using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted))) - using (Plugin.FontManager.FontAwesome.Push()) - { - ImGui.TextUnformatted(FontAwesomeIcon.Hourglass.ToIconString()); - } - ImGui.SameLine(); - ImGui.TextUnformatted(title); - using (ImRaii.PushIndent()) - { - using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted))) - { - ImGui.TextWrapped(description); - } - } - ImGui.Spacing(); - } - - private void DrawGotAnIdeaSection() - { - DrawSectionHeader(HellionStrings.Settings_Integrations_GotAnIdea_SectionHeader); - ImGui.TextWrapped(HellionStrings.Settings_Integrations_GotAnIdea_Body); - ImGui.Spacing(); - - if (ImGui.Button(HellionStrings.Settings_Integrations_GotAnIdea_LinkLabel)) - { - Plugin.PlatformUtil.OpenLink(BrandingLinks.HellionForgeDiscordInvite); - } - } - - private void DrawSectionHeader(string label) - { - var theme = Plugin.ThemeRegistry.Active; - using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.Primary))) - { - ImGui.TextUnformatted("── " + label + " ──"); - } - } - - // ── Plugin info ────────────────────────────────────────────────────────── - - private void DrawPluginInfoSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_PluginInfo); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - DrawFoxBanner(); - ImGuiHelpers.ScaledDummy(6.0f); - - ImGui.TextUnformatted(string.Format(Language.Options_About_Opening, Plugin.PluginName)); - - ImGuiHelpers.ScaledDummy(10.0f); - - ImGui.TextUnformatted(Language.Options_About_Authors); - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.ParsedGold, Plugin.Interface.Manifest.Author); - - ImGui.TextUnformatted(Language.Options_About_Discord); - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.ParsedGold, "@j.j_kazama"); - - ImGui.TextUnformatted(Language.Options_About_Version); - ImGui.SameLine(); - ImGui.TextColored( - ImGuiColors.ParsedOrange, - Plugin.Interface.Manifest.AssemblyVersion.ToString(3) - ); - - ImGuiHelpers.ScaledDummy(10.0f); - - ImGui.TextUnformatted(Language.Options_About_Github_Issues); - ImGui.SameLine(); - if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues")) - Plugin.PlatformUtil.OpenLink( - "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues" - ); - } - } - - private void DrawFoxBanner() - { - var banner = FoxBannerTexture.Shared.GetWrapOrDefault(); - if (banner is null) - return; - - const uint CardColor = 0xFFE8E8E8; // off-white fill so the dark fox pops - var imgHeight = 170f * ImGuiHelpers.GlobalScale; - var imgWidth = imgHeight * banner.Size.X / banner.Size.Y; - var pad = 14f * ImGuiHelpers.GlobalScale; - var cardWidth = imgWidth + pad * 2f; - var cardHeight = imgHeight + pad * 2f; - var rounding = 8f * ImGuiHelpers.GlobalScale; - - // Left-aligned: card origin stays at the current layout cursor position. - var cardOrigin = ImGui.GetCursorScreenPos(); - - // Draw the rounded card behind the image, then place the image on top. - ImGui - .GetWindowDrawList() - .AddRectFilled( - cardOrigin, - cardOrigin + new Vector2(cardWidth, cardHeight), - CardColor, - rounding - ); - ImGui.SetCursorScreenPos(cardOrigin + new Vector2(pad, pad)); - ImGui.Image(banner.Handle, new Vector2(imgWidth, imgHeight)); - - // Advance the layout cursor past the full card so content below does not overlap. - ImGui.SetCursorScreenPos(cardOrigin); - ImGui.Dummy(new Vector2(cardWidth, cardHeight)); - } - - // ── The Project ────────────────────────────────────────────────────────── - - private void DrawProjectSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Project); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Maintainer_Heading); - ImGui.TextUnformatted(HellionStrings.About_Maintainer_Body); - ImGui.TextUnformatted(HellionStrings.About_Maintainer_Website_Label); - ImGui.SameLine(); - if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "hellionMedia")) - Plugin.PlatformUtil.OpenLink("https://hellion-media.de"); - - ImGuiHelpers.ScaledDummy(10.0f); - - ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Mission_Heading); - ImGui.TextUnformatted(HellionStrings.About_Mission_P1); - ImGui.Spacing(); - ImGui.TextUnformatted(HellionStrings.About_Mission_P2); - ImGui.Spacing(); - ImGui.TextUnformatted(HellionStrings.About_Mission_P3); - - ImGuiHelpers.ScaledDummy(10.0f); - - ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_BuiltOn_Heading); - ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P1); - ImGui.Spacing(); - ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P2); - ImGui.Spacing(); - ImGui.TextUnformatted(HellionStrings.About_BuiltOn_Upstream_Label); - ImGui.SameLine(); - if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "chatTwoUpstream")) - Plugin.PlatformUtil.OpenLink("https://github.com/Infiziert90/ChatTwo"); - - ImGuiHelpers.ScaledDummy(10.0f); - - ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_License_Heading); - ImGui.TextUnformatted(HellionStrings.About_License_P1); - ImGui.TextUnformatted(HellionStrings.About_License_P2); - ImGui.TextUnformatted(HellionStrings.About_License_P3); - - ImGuiHelpers.ScaledDummy(10.0f); - - ImGui.TextColored(ImGuiColors.DalamudOrange, HellionStrings.About_SE_Heading); - ImGui.TextUnformatted(HellionStrings.About_SE_P1); - ImGui.TextUnformatted(HellionStrings.About_SE_P2); - - ImGui.Spacing(); - - ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Localization_Heading); - ImGui.TextUnformatted(HellionStrings.About_Localization_P1); - ImGui.TextUnformatted(HellionStrings.About_Localization_P2); - } - } - - // ── Translators ────────────────────────────────────────────────────────── - - private void DrawTranslatorsSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Translators); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - // The translator list belongs to the Chat 2 upstream Crowdin project. - using var translatorTree = ImRaii.TreeNode(HellionStrings.About_Translators_TreeNode); - if (translatorTree) - { - using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false); - foreach (var translator in Translators) - ImGui.TextUnformatted(translator); - } - } - } - - // ── Changelog ──────────────────────────────────────────────────────────── - - private void DrawChangelogSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Changelog); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_PrintChangelog_Name, ref Mutable.PrintChangelog); - ImGuiUtil.HelpMarker(Language.Options_PrintChangelog_Description); - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - var changelog = Plugin.Interface.Manifest.Changelog; - if (changelog == null) - return; - - ImGui.TextUnformatted(Language.Options_Changelog_Header); - ImGui.TextUnformatted( - $"Version {Plugin.Interface.Manifest.AssemblyVersion.ToString(3)}" - ); - ImGui.Spacing(); - foreach (var sentence in changelog.Split("\n")) - { - if (sentence == string.Empty) - { - ImGui.NewLine(); - continue; - } - - var indented = sentence.StartsWith('-') || sentence.StartsWith(" -"); - using var indent = ImRaii.PushIndent(10.0f, true, indented); - ImGui.TextUnformatted(sentence); - } - } - } -} diff --git a/HellionChat/Ui/SettingsTabs/Appearance.cs b/HellionChat/Ui/SettingsTabs/Appearance.cs deleted file mode 100644 index aa3044c..0000000 --- a/HellionChat/Ui/SettingsTabs/Appearance.cs +++ /dev/null @@ -1,695 +0,0 @@ -using System.Numerics; -using Dalamud; -using Dalamud.Bindings.ImGui; -using Dalamud.Interface; -using Dalamud.Interface.FontIdentifier; -using Dalamud.Interface.Utility; -using Dalamud.Interface.Utility.Raii; -using HellionChat.Code; -using HellionChat.Resources; -using HellionChat.Themes; -using HellionChat.Util; -using Microsoft.Extensions.Logging; - -namespace HellionChat.Ui.SettingsTabs; - -internal sealed class Appearance : ISettingsTab -{ - private Plugin Plugin { get; } - private Configuration Mutable { get; } - private readonly ILogger _logger; - - private string? _applyDismissedFor; - - public string Name => HellionStrings.Settings_Tab_Appearance + "###tabs-appearance"; - - internal Appearance(Plugin plugin, Configuration mutable, ILogger logger) - { - Plugin = plugin; - Mutable = mutable; - _logger = logger; - } - - public void Draw(bool sectionJustEntered) - { - DrawThemeSection(sectionJustEntered); - ImGui.Spacing(); - DrawFontsSection(sectionJustEntered); - ImGui.Spacing(); - DrawColoursSection(sectionJustEntered); - ImGui.Spacing(); - DrawWindowStyleSection(sectionJustEntered); - ImGui.Spacing(); - DrawTimestampSection(sectionJustEntered); - ImGui.Spacing(); - DrawAnimationsSection(sectionJustEntered); - } - - // ── Theme ────────────────────────────────────────────────────────────── - - private void DrawThemeSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Theme); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - var registry = Plugin.ThemeRegistry; - var active = registry.Get(Mutable.Theme); - - ImGui.TextUnformatted( - string.Format(HellionStrings.Settings_Themes_Active, active.Name) - ); - using (ImRaii.PushColor(ImGuiCol.Text, 0xFF8FA3B5u)) - ImGui.TextUnformatted(active.Author); - - DrawChatColorsApplyBanner(active); - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - ImGui.TextUnformatted(HellionStrings.Settings_Themes_BuiltIns); - ImGui.Spacing(); - DrawThemeGrid(registry.AllBuiltIns(), active.Slug); - - var customs = registry.AllCustom().ToList(); - if (customs.Count > 0) - { - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - ImGui.TextUnformatted(HellionStrings.Settings_Themes_Custom); - ImGui.Spacing(); - DrawThemeGrid(customs, active.Slug); - } - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - if (ImGui.Button(HellionStrings.Settings_Themes_OpenFolder)) - { - var dir = Path.Combine(Plugin.Interface.ConfigDirectory.FullName, "themes"); - Directory.CreateDirectory(dir); - Plugin.PlatformUtil.OpenLink(dir); - } - - ImGui.SameLine(); - if (ImGui.Button(HellionStrings.Settings_Themes_ExportActive)) - { - var dir = Path.Combine(Plugin.Interface.ConfigDirectory.FullName, "themes"); - Directory.CreateDirectory(dir); - var fileName = $"{active.Slug}.export.json"; - var path = Path.Combine(dir, fileName); - var json = ThemeJsonWriter.Serialize(active); - File.WriteAllText(path, json); - _logger.LogInformation($"Exported active theme '{active.Slug}' to {path}"); - } - } - } - - private void DrawThemeGrid(IEnumerable themes, string activeSlug) - { - var avail = ImGui.GetContentRegionAvail(); - var columns = avail.X >= 700f ? 3 : 2; - var cardWidth = (avail.X - (columns - 1) * 8f) / columns; - var cardHeight = 140f; - - var list = themes.ToList(); - for (var i = 0; i < list.Count; i++) - { - DrawThemeCard(list[i], activeSlug, cardWidth, cardHeight); - - if ((i + 1) % columns != 0 && i != list.Count - 1) - ImGui.SameLine(); - } - } - - private void DrawThemeCard(Theme theme, string activeSlug, float w, float h) - { - ImGui.BeginGroup(); - - var isActive = string.Equals(theme.Slug, activeSlug, StringComparison.OrdinalIgnoreCase); - var cursorBefore = ImGui.GetCursorScreenPos(); - var clicked = ImGui.InvisibleButton($"##theme-card-{theme.Slug}", new Vector2(w, h)); - var hovered = ImGui.IsItemHovered(); - - var draw = ImGui.GetWindowDrawList(); - var bg = ColourUtil.RgbaToAbgr(theme.Colors.WindowBg | 0xFFu); - draw.AddRectFilled(cursorBefore, cursorBefore + new Vector2(w, h), bg, 4f); - - if (isActive) - { - var border = ColourUtil.RgbaToAbgr(theme.Colors.Primary); - draw.AddRect( - cursorBefore, - cursorBefore + new Vector2(w, h), - border, - 4f, - ImDrawFlags.None, - 2f - ); - } - else if (hovered) - { - var border = ColourUtil.RgbaToAbgr(theme.Colors.PrimaryLight & 0xFFFFFF99u); - draw.AddRect( - cursorBefore, - cursorBefore + new Vector2(w, h), - border, - 4f, - ImDrawFlags.None, - 1f - ); - } - - var mockupOrigin = cursorBefore + new Vector2(12f, 12f); - var mockupSize = new Vector2(w - 24f, 60f); - ThemeMockup.Draw(mockupOrigin, mockupSize, theme); - - var textColor = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary); - var mutedColor = ColourUtil.RgbaToAbgr(theme.Colors.TextMuted); - draw.AddText(cursorBefore + new Vector2(12f, 80f), textColor, theme.Name); - draw.AddText(cursorBefore + new Vector2(12f, 100f), mutedColor, theme.Author); - - ImGui.EndGroup(); - - if (clicked) - { - Mutable.Theme = theme.Slug; - Plugin.ThemeRegistry.Switch(theme.Slug); - _applyDismissedFor = null; - } - } - - private void DrawChatColorsApplyBanner(Theme active) - { - if (active.ChatColors is not { Channels.Count: > 0 } themeChatColors) - return; - - if (_applyDismissedFor == active.Slug) - return; - - var alreadyMatching = themeChatColors.Channels.All(kvp => - Mutable.ChatColours.TryGetValue(kvp.Key, out var current) && current == kvp.Value - ); - if (alreadyMatching) - return; - - ImGui.Spacing(); - - var border = ColourUtil.RgbaToAbgr(active.Colors.Primary); - var bgFill = ColourUtil.RgbaToAbgr((active.Colors.Surface & 0xFFFFFF00u) | 0xCCu); - var origin = ImGui.GetCursorScreenPos(); - var width = ImGui.GetContentRegionAvail().X; - var height = 64f; - var draw = ImGui.GetWindowDrawList(); - draw.AddRectFilled(origin, origin + new Vector2(width, height), bgFill, 4f); - draw.AddRect(origin, origin + new Vector2(width, height), border, 4f, ImDrawFlags.None, 1f); - - var textColor = ColourUtil.RgbaToAbgr(active.Colors.TextPrimary); - draw.AddText( - origin + new Vector2(12f, 10f), - textColor, - HellionStrings.Settings_Themes_ApplyChatColors_Hint - ); - - using (ImRaii.PushColor(ImGuiCol.Button, active.Colors.Primary)) - using (ImRaii.PushColor(ImGuiCol.ButtonHovered, active.Colors.PrimaryLight)) - using (ImRaii.PushColor(ImGuiCol.ButtonActive, active.Colors.PrimaryDark)) - { - ImGui.SetCursorScreenPos(origin + new Vector2(12f, 32f)); - if (ImGui.Button(HellionStrings.Settings_Themes_ApplyChatColors_Apply)) - { - foreach (var kvp in themeChatColors.Channels) - Mutable.ChatColours[kvp.Key] = kvp.Value; - _applyDismissedFor = active.Slug; - } - } - - ImGui.SameLine(); - if (ImGui.Button(HellionStrings.Settings_Themes_ApplyChatColors_Keep)) - { - _applyDismissedFor = active.Slug; - } - - ImGui.SetCursorScreenPos(origin + new Vector2(0f, height + 8f)); - - ImGui.Spacing(); - } - - // ── Fonts ────────────────────────────────────────────────────────────── - // R3 deliberately NOT applied here — the UseHellionFont/FontsEnabled - // visibility chain has priority over type grouping (R4). - - private void DrawFontsSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Fonts); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - if ( - ImGui.Checkbox(HellionStrings.Theme_UseHellionFont_Name, ref Mutable.UseHellionFont) - ) - { - if (Mutable.UseHellionFont) - Mutable.FontsEnabled = false; - } - ImGuiUtil.HelpMarker(HellionStrings.Theme_UseHellionFont_Description); - ImGui.Spacing(); - - if (Mutable.UseHellionFont) - { - // Bundled-font path: only the base font size matters; the - // global / japanese / italic chooser pickers do not apply. - ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2); - ImGui.Spacing(); - } - else - { - ImGui.Checkbox(Language.Options_FontsEnabled, ref Mutable.FontsEnabled); - ImGui.Spacing(); - } - - var unused = false; - if (!Mutable.UseHellionFont && !Mutable.FontsEnabled) - { - ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2); - } - else if (!Mutable.UseHellionFont) - { - var globalChooser = ImGuiUtil.FontChooser( - Language.Options_Font_Name, - Mutable.GlobalFontV2, - false, - ref unused - ); - globalChooser?.ResultTask.ContinueWith(r => - { - if (r.IsCompletedSuccessfully) - { - Plugin.Framework.Run(() => Mutable.GlobalFontV2 = r.Result); - } - }); - ImGui.SameLine(); - if (ImGui.Button("Reset##global")) - { - Mutable.GlobalFontV2 = new SingleFontSpec - { - FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), - SizePt = 12.75f, - }; - } - - ImGuiUtil.HelpMarker( - string.Format(Language.Options_Font_Description, Plugin.PluginName) - ); - ImGuiUtil.WarningText(Language.Options_Font_Warning); - ImGui.Spacing(); - - var japaneseChooser = ImGuiUtil.FontChooser( - Language.Options_JapaneseFont_Name, - Mutable.JapaneseFontV2, - false, - ref unused, - id => !id.LocaleNames?.ContainsKey("ja-jp") ?? false, - "いろはにほへと ちりぬるを" - ); - japaneseChooser?.ResultTask.ContinueWith(r => - { - if (r.IsCompletedSuccessfully) - { - Plugin.Framework.Run(() => Mutable.JapaneseFontV2 = r.Result); - } - }); - ImGui.SameLine(); - if (ImGui.Button("Reset##japanese")) - { - Mutable.JapaneseFontV2 = new SingleFontSpec - { - FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkMedium), - SizePt = 12.75f, - }; - } - - ImGuiUtil.HelpMarker( - string.Format(Language.Options_JapaneseFont_Description, Plugin.PluginName) - ); - ImGui.Spacing(); - - var italicChooser = ImGuiUtil.FontChooser( - Language.Options_ItalicFont_Name, - Mutable.ItalicFontV2, - true, - ref Mutable.ItalicEnabled - ); - italicChooser?.ResultTask.ContinueWith(r => - { - if (r.IsCompletedSuccessfully) - { - Plugin.Framework.Run(() => Mutable.ItalicFontV2 = r.Result); - } - }); - ImGui.SameLine(); - if (ImGui.Button("Reset##italic")) - { - Mutable.ItalicEnabled = false; - Mutable.ItalicFontV2 = new SingleFontSpec - { - FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), - SizePt = 12.75f, - }; - } - - ImGuiUtil.HelpMarker( - string.Format(Language.Options_Italic_Description, Plugin.PluginName) - ); - ImGui.Spacing(); - } - - // v1.5.3: ExtraGlyphRanges is an atlas-wide property and stays - // reachable regardless of UseHellionFont / FontsEnabled state so - // users can verify or override the auto-activation on language change. - ImGui.Spacing(); - if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name)) - { - ImGuiUtil.HelpMarker( - string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName) - ); - - var range = (int)Mutable.ExtraGlyphRanges; - foreach (var extra in Enum.GetValues()) - { - ImGui.CheckboxFlags(extra.Name(), ref range, (int)extra); - } - - Mutable.ExtraGlyphRanges = (ExtraGlyphRanges)range; - } - - ImGuiUtil.FontSizeCombo( - Language.Options_SymbolsFontSize_Name, - ref Mutable.SymbolsFontSizeV2 - ); - ImGuiUtil.HelpMarker(Language.Options_SymbolsFontSize_Description); - - ImGui.Spacing(); - } - } - - // ── Colours ──────────────────────────────────────────────────────────── - - private void DrawColoursSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Colours); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - DrawColourPresetButtons(); - ImGui.TextDisabled(HellionStrings.Settings_Appearance_Colours_PresetsHint); - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - ImGui.Checkbox( - Language.Options_ColorSelectedInputChannelButton_Name, - ref Mutable.ColorSelectedInputChannelButton - ); - ImGuiUtil.HelpMarker(Language.Options_ColorSelectedInputChannelButton_Description); - ImGui.Spacing(); - - foreach (var (_, types) in ChatTypeExt.SortOrder) - { - foreach (var type in types) - { - if ( - ImGuiUtil.IconButton( - FontAwesomeIcon.UndoAlt, - $"{type}", - Language.Options_ChatColours_Reset - ) - ) - { - Mutable.ChatColours.Remove(type); - } - - ImGui.SameLine(); - - if ( - ImGuiUtil.IconButton( - FontAwesomeIcon.LongArrowAltDown, - $"{type}", - Language.Options_ChatColours_Import - ) - ) - { - var gameColour = Plugin.Functions.Chat.GetChannelColor(type); - Mutable.ChatColours[type] = gameColour ?? type.DefaultColor() ?? 0; - } - - ImGui.SameLine(); - - var vec = Mutable.ChatColours.TryGetValue(type, out var colour) - ? ColourUtil.RgbaToVector3(colour) - : ColourUtil.RgbaToVector3(type.DefaultColor() ?? 0); - if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs)) - { - Mutable.ChatColours[type] = ColourUtil.Vector3ToRgba(vec); - } - } - } - - ImGui.Spacing(); - } - } - - private void DrawColourPresetButtons() - { - var first = true; - foreach (var (_, preset) in ChatColourPresets.All) - { - if (!first) - { - ImGui.SameLine(); - } - first = false; - - if (preset.IsBrandPreset) - { - var border = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(255, 128, 200)); - var btn = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(74, 42, 106)); - ImGui.PushStyleColor( - ImGuiCol.Border, - new System.Numerics.Vector4(border.X, border.Y, border.Z, 1f) - ); - ImGui.PushStyleColor( - ImGuiCol.Button, - new System.Numerics.Vector4(btn.X, btn.Y, btn.Z, 1f) - ); - ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1.5f); - } - - if (ImGui.Button(GetPresetLabel(preset))) - { - ApplyPreset(preset); - } - - if (preset.IsBrandPreset) - { - ImGui.PopStyleVar(); - ImGui.PopStyleColor(2); - } - } - } - - private static string GetPresetLabel(ChatColourPreset preset) - { - var localized = HellionStrings.ResourceManager.GetString( - preset.LocalizationKey, - HellionStrings.Culture - ); - return string.IsNullOrEmpty(localized) ? preset.DisplayName : localized; - } - - private void ApplyPreset(ChatColourPreset preset) - { - foreach (var (channel, colour) in preset.Colours) - { - Mutable.ChatColours[channel] = colour; - } - Plugin.SaveConfig(); - GlobalParametersCache.Refresh(); - _logger.LogDebug($"Applied chat colour preset: {preset.DisplayName}"); - } - - // ── Window style ─────────────────────────────────────────────────────── - - private void DrawWindowStyleSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_WindowStyle); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_ShowTitleBar_Name, ref Mutable.ShowTitleBar); - - ImGui.Checkbox( - Language.Options_ShowPopOutTitleBar_Name, - ref Mutable.ShowPopOutTitleBar - ); - - ImGui.Checkbox(Language.Options_ShowHideButton_Name, ref Mutable.ShowHideButton); - ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description); - - ImGui.Checkbox(Language.Options_SidebarTabView_Name, ref Mutable.SidebarTabView); - ImGuiUtil.HelpMarker( - string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName) - ); - - if (Mutable.SidebarTabView) - { - var sidebarWidth = Mutable.SidebarWidth; - if ( - ImGui.SliderInt( - HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Name, - ref sidebarWidth, - 44, - 160, - $"{sidebarWidth} px" - ) - ) - { - Mutable.SidebarWidth = sidebarWidth; - } - ImGuiUtil.HelpMarker( - HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Description - ); - } - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - // Slider range 50-100% maps to 0.5-1.0 internally. Floor at 50% prevents - // accidentally hiding the chat background (v1.2.0 bug at WindowAlpha=0). - var opacityPercent = Mutable.WindowOpacity * 100f; - if ( - ImGuiUtil.DragFloatVertical( - HellionStrings.Settings_ThemeAndLayout_WindowOpacity_Name, - ref opacityPercent, - .25f, - 50f, - 100f, - $"{opacityPercent:N0}%%", - ImGuiSliderFlags.AlwaysClamp - ) - ) - { - Mutable.WindowOpacity = opacityPercent / 100f; - } - ImGuiUtil.HelpMarker(HellionStrings.Settings_ThemeAndLayout_WindowOpacity_Description); - - // UI-12: inactive-window opacity, same 50-100% range and clamp. - var inactiveOpacityPercent = Mutable.WindowOpacityInactive * 100f; - if ( - ImGuiUtil.DragFloatVertical( - HellionStrings.Settings_ThemeAndLayout_WindowOpacityInactive_Name, - ref inactiveOpacityPercent, - .25f, - 50f, - 100f, - $"{inactiveOpacityPercent:N0}%%", - ImGuiSliderFlags.AlwaysClamp - ) - ) - { - Mutable.WindowOpacityInactive = inactiveOpacityPercent / 100f; - } - ImGuiUtil.HelpMarker( - HellionStrings.Settings_ThemeAndLayout_WindowOpacityInactive_Description - ); - } - } - - // ── Timestamps ───────────────────────────────────────────────────────── - - private void DrawTimestampSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Timestamps); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox( - Language.Options_PrettierTimestamps_Name, - ref Mutable.PrettierTimestamps - ); - ImGuiUtil.HelpMarker(Language.Options_PrettierTimestamps_Description); - - if (Mutable.PrettierTimestamps) - { - ImGui.Checkbox( - Language.Options_MoreCompactPretty_Name, - ref Mutable.MoreCompactPretty - ); - ImGuiUtil.HelpMarker(Language.Options_MoreCompactPretty_Description); - - ImGui.Checkbox( - HellionStrings.Appearance_UseCompactDensity_Name, - ref Mutable.UseCompactDensity - ); - ImGuiUtil.HelpMarker(HellionStrings.Appearance_UseCompactDensity_Description); - - ImGui.Checkbox( - Language.Options_HideSameTimestamps_Name, - ref Mutable.HideSameTimestamps - ); - ImGuiUtil.HelpMarker(Language.Options_HideSameTimestamps_Description); - } - - ImGui.Checkbox(Language.Options_Use24HourClock_Name, ref Mutable.Use24HourClock); - ImGuiUtil.HelpMarker(Language.Options_Use24HourClock_Description); - } - } - - // ── Animations ───────────────────────────────────────────────────────── - - private void DrawAnimationsSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Animations); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - // Master accessibility toggle for the v1.5.4 motion work: the - // theme crossfade, the sidebar/card hover lerps and the - // unread-tab pulse all read Config.ReduceMotion and snap - // instantly when it is on. - ImGui.Checkbox( - HellionStrings.Settings_ThemeAndLayout_ReduceMotion_Name, - ref Mutable.ReduceMotion - ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_ThemeAndLayout_ReduceMotion_Description); - } - } -} diff --git a/HellionChat/Ui/SettingsTabs/Chat.cs b/HellionChat/Ui/SettingsTabs/Chat.cs deleted file mode 100644 index 0561af3..0000000 --- a/HellionChat/Ui/SettingsTabs/Chat.cs +++ /dev/null @@ -1,423 +0,0 @@ -using System.Numerics; -using Dalamud.Bindings.ImGui; -using Dalamud.Interface; -using Dalamud.Interface.Colors; -using Dalamud.Interface.Utility; -using Dalamud.Interface.Utility.Raii; -using HellionChat.Resources; -using HellionChat.Util; - -namespace HellionChat.Ui.SettingsTabs; - -// Six sections: Messages, Input & preview, Auto-tell tabs, Emotes, Links & tooltips, Novice network. -internal sealed class Chat : ISettingsTab -{ - private Plugin Plugin { get; } - private Configuration Mutable { get; } - - public string Name => HellionStrings.Settings_Tab_Chat + "###tabs-chat"; - - private SearchSelector.SelectorPopupOptions WordPopupOptions; - - // Tracks which EmoteCache state WordPopupOptions was built for so we - // don't refill every frame when FilteredSheet is empty. - private EmoteCache.LoadingState? WordPopupOptionsBuiltFor; - - internal Chat(Plugin plugin, Configuration mutable) - { - Plugin = plugin; - Mutable = mutable; - - WordPopupOptions = RefillSheet(); - WordPopupOptionsBuiltFor = EmoteCache.State; - } - - private SearchSelector.SelectorPopupOptions RefillSheet() => - new SearchSelector.SelectorPopupOptions - { - FilteredSheet = EmoteCache - .SortedCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w)) - .ToArray(), - }; - - public void Draw(bool sectionJustEntered) - { - DrawMessagesSection(sectionJustEntered); - ImGui.Spacing(); - DrawInputPreviewSection(sectionJustEntered); - ImGui.Spacing(); - DrawAutoTellTabsSection(sectionJustEntered); - ImGui.Spacing(); - DrawEmotesSection(sectionJustEntered); - ImGui.Spacing(); - DrawLinksTooltipsSection(sectionJustEntered); - ImGui.Spacing(); - DrawNoviceNetworkSection(sectionJustEntered); - } - - private void DrawMessagesSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Messages); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - // Checkboxes first. - ImGui.Checkbox( - Language.Options_CollapseDuplicateMessages_Name, - ref Mutable.CollapseDuplicateMessages - ); - ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMessages_Description); - - // Conditional child: only visible when parent is on (R4). - if (Mutable.CollapseDuplicateMessages) - { - ImGui.Checkbox( - Language.Options_CollapseDuplicateMsgUniqueLink_Name, - ref Mutable.CollapseKeepUniqueLinks - ); - ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMsgUniqueLink_Description); - } - - ImGui.Checkbox( - HellionStrings.Settings_Chat_NotifyFailedTell_Name, - ref Mutable.NotifyFailedTell - ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_NotifyFailedTell_Description); - - ImGui.Checkbox( - HellionStrings.Settings_Chat_NotifyPluginDisclosure_Name, - ref Mutable.NotifyPluginDisclosure - ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_NotifyPluginDisclosure_Description); - - // Dropdowns after checkboxes (R3). - // UI-7: name display options. - using ( - var combo = ImGuiUtil.BeginComboVertical( - HellionStrings.Settings_Chat_WorldSuffix_Name, - Mutable.WorldSuffixMode.Name() - ) - ) - { - if (combo.Success) - { - foreach (var mode in Enum.GetValues()) - { - if (ImGui.Selectable(mode.Name(), Mutable.WorldSuffixMode == mode)) - Mutable.WorldSuffixMode = mode; - } - } - } - ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_WorldSuffix_Description); - - using ( - var combo = ImGuiUtil.BeginComboVertical( - HellionStrings.Settings_Chat_NameForm_Name, - Mutable.NameFormMode.Name() - ) - ) - { - if (combo.Success) - { - foreach (var mode in Enum.GetValues()) - { - if (ImGui.Selectable(mode.Name(), Mutable.NameFormMode == mode)) - Mutable.NameFormMode = mode; - } - } - } - ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_NameForm_Description); - } - } - - private void DrawInputPreviewSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_InputPreview); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - // Checkboxes first. - ImGui.Checkbox( - HellionStrings.Settings_Chat_SymbolPicker_Enable_Name, - ref Mutable.SymbolPickerEnabled - ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_SymbolPicker_Enable_Description); - - ImGui.Checkbox(Language.Options_PreviewOnlyIf_Name, ref Mutable.OnlyPreviewIf); - ImGuiUtil.HelpMarker(Language.Options_PreviewOnlyIf_Description); - - // Dropdown after checkboxes (R3). - using ( - var combo = ImGuiUtil.BeginComboVertical( - Language.Options_Preview_Name, - Mutable.PreviewPosition.Name() - ) - ) - { - if (combo) - { - foreach (var position in Enum.GetValues()) - { - if (ImGui.Selectable(position.Name(), Mutable.PreviewPosition == position)) - Mutable.PreviewPosition = position; - } - } - } - ImGuiUtil.HelpMarker(Language.Options_Preview_Description); - - // Number input last (R3). - if ( - ImGuiUtil.InputIntVertical( - Language.Options_PreviewMinimum_Name, - Language.Options_PreviewMinimum_Description, - ref Mutable.PreviewMinimum - ) - ) - Mutable.PreviewMinimum = Math.Clamp(Mutable.PreviewMinimum, 1, 250); - } - } - - private void DrawAutoTellTabsSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_AutoTellTabs); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - // Checkboxes first (R3). - ImGui.Checkbox( - HellionStrings.ChatLog_AutoTellTabs_Enable_Name, - ref Mutable.EnableAutoTellTabs - ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Enable_Description); - - ImGui.Checkbox( - HellionStrings.ChatLog_AutoTellTabs_Compact_Name, - ref Mutable.AutoTellTabsCompactDisplay - ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Compact_Description); - - ImGui.Checkbox( - HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Name, - ref Mutable.AutoTellTabsOpenAsPopout - ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Description); - - ImGui.Checkbox( - HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Name, - ref Mutable.AutoTellTabsShowGreetedToggle - ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Description); - - // Sliders after checkboxes (R3). - ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale); - var limit = Mutable.AutoTellTabsLimit; - if (ImGui.SliderInt(HellionStrings.ChatLog_AutoTellTabs_Limit_Name, ref limit, 1, 50)) - Mutable.AutoTellTabsLimit = limit; - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Limit_Description); - - ImGui.Spacing(); - ImGuiUtil.HelpText(HellionStrings.ChatLog_AutoTellTabs_PreloadHint); - - ImGui.Spacing(); - ImGuiUtil.WarningText(HellionStrings.ChatLog_AutoTellTabs_ConflictHint); - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - var preload = Mutable.AutoTellTabsHistoryPreload; - ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale); - if ( - ImGui.SliderInt( - HellionStrings.Privacy_AutoTellTabs_Preload_Name, - ref preload, - 0, - 100 - ) - ) - Mutable.AutoTellTabsHistoryPreload = preload; - ImGuiUtil.HelpMarker(HellionStrings.Privacy_AutoTellTabs_Preload_Description); - - ImGui.Spacing(); - ImGuiUtil.HelpText(HellionStrings.Privacy_AutoTellTabs_Preload_Hint); - } - } - - private void DrawEmotesSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Emotes); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - // Checkbox first (R3). - ImGui.Checkbox(Language.Options_ShowEmotes_Name, ref Mutable.ShowEmotes); - ImGuiUtil.HelpMarker(Language.Options_ShowEmotes_Desc); - - ImGui.Spacing(); - ImGui.TextUnformatted(Language.Options_Emote_BlockedEmotes); - ImGui.Spacing(); - - if ( - EmoteCache.State is EmoteCache.LoadingState.Done - && WordPopupOptions.FilteredSheet.Length == 0 - && WordPopupOptionsBuiltFor != EmoteCache.LoadingState.Done - ) - { - WordPopupOptions = RefillSheet(); - WordPopupOptionsBuiltFor = EmoteCache.LoadingState.Done; - } - - // Button to add blocked emotes (R3 — button before table). - var buttonWidth = ImGui.GetContentRegionAvail().X / 3; - using (Plugin.FontManager.FontAwesome.Push()) - ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(buttonWidth, 0)); - - // OpenPopup on click because SelectorPopup uses ContextPopupItem - // which only triggers on right-click by default. - if (ImGui.IsItemClicked()) - ImGui.OpenPopup("WordAddPopup"); - - if (SearchSelector.SelectorPopup("WordAddPopup", out var newWord, WordPopupOptions)) - Mutable.BlockedEmotes.Add(newWord); - - using ( - var table = ImRaii.Table( - "##BlockedWords", - 2, - ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner - ) - ) - { - if (table) - { - ImGui.TableSetupColumn(Language.Options_Emote_EmoteTable); - ImGui.TableSetupColumn("##Del", ImGuiTableColumnFlags.WidthStretch, 0.07f); - ImGui.TableHeadersRow(); - - foreach (var word in Mutable.BlockedEmotes.ToArray()) - { - ImGui.TableNextColumn(); - ImGui.TextUnformatted(word); - - ImGui.TableNextColumn(); - if ( - ImGuiUtil.Button( - $"##{word}Del", - FontAwesomeIcon.Trash, - !ImGui.GetIO().KeyCtrl - ) - ) - Mutable.BlockedEmotes.Remove(word); - } - } - } - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - ImGui.TextUnformatted(Language.Options_Emote_EmoteStats); - ImGui.Spacing(); - - if (EmoteCache.State is EmoteCache.LoadingState.Done) - ImGui.TextColored(ImGuiColors.HealerGreen, Language.Options_Emote_Ready); - else - ImGui.TextColored(ImGuiColors.DPSRed, Language.Options_Emote_NotReady); - - ImGui.TextUnformatted( - $"{Language.Options_Emote_Loaded} {EmoteCache.SortedCodeArray.Length}" - ); - - // 5-column loaded-emotes display table. - using ( - var emoteTable = ImRaii.Table( - "##LoadedEmotes", - 5, - ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner - ) - ) - { - if (emoteTable) - { - ImGui.TableSetupColumn("##word1"); - ImGui.TableSetupColumn("##word2"); - ImGui.TableSetupColumn("##word3"); - ImGui.TableSetupColumn("##word4"); - ImGui.TableSetupColumn("##word5"); - - foreach (var word in EmoteCache.SortedCodeArray) - { - ImGui.TableNextColumn(); - ImGui.TextUnformatted(word); - } - } - } - } - } - - private void DrawLinksTooltipsSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_LinksTooltips); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox( - Language.Options_NativeItemTooltips_Name, - ref Mutable.NativeItemTooltips - ); - ImGuiUtil.HelpMarker( - string.Format(Language.Options_NativeItemTooltips_Description, Plugin.PluginName) - ); - - // Conditional slider: only shown when native tooltips are enabled (R4). - if (Mutable.NativeItemTooltips) - { - ImGuiUtil.DragFloatVertical( - Language.Options_TooltipOffset_Name, - Language.Options_TooltipOffset_Desc, - ref Mutable.TooltipOffset, - 1, - 0f, - 400f, - $"{Mutable.TooltipOffset:N0}px", - ImGuiSliderFlags.AlwaysClamp - ); - } - } - } - - private void DrawNoviceNetworkSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_NoviceNetwork); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_ShowNoviceNetwork_Name, ref Mutable.ShowNoviceNetwork); - ImGuiUtil.HelpMarker(Language.Options_ShowNoviceNetwork_Description); - } - } -} diff --git a/HellionChat/Ui/SettingsTabs/DataAndPrivacy.cs b/HellionChat/Ui/SettingsTabs/DataAndPrivacy.cs deleted file mode 100644 index 0d2f390..0000000 --- a/HellionChat/Ui/SettingsTabs/DataAndPrivacy.cs +++ /dev/null @@ -1,1097 +0,0 @@ -using System.Diagnostics; -using Dalamud.Bindings.ImGui; -using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; -using Dalamud.Interface.Colors; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.Utility.Raii; -using HellionChat.Code; -using HellionChat.Export; -using HellionChat.Privacy; -using HellionChat.Resources; -using HellionChat.Util; -using Microsoft.Extensions.Logging; - -namespace HellionChat.Ui.SettingsTabs; - -internal sealed class DataAndPrivacy : ISettingsTab -{ - private Plugin Plugin { get; } - private Configuration Mutable { get; } - private readonly ILogger _logger; - - public string Name => - HellionStrings.Settings_Card_DataManagement_Title + "###tabs-datamanagement"; - - // Cleanup state - private Dictionary? CleanupCounts; - private long CleanupKeepCount; - private long CleanupDeleteCount; - private bool CleanupRunning; - private bool CleanupPreviewStale; - private HashSet? CleanupPreviewSnapshot; - private bool RetentionRunning => Plugin.RetentionSweepRunning; - - // Export form state - private int ExportRangeDays = 30; - private string ExportSenderSubstring = string.Empty; - private readonly HashSet ExportSelectedChannels = []; - private ExportFormat ExportFormat = ExportFormat.Markdown; - private bool ExportRunning; - - // DB-Viewer + Advanced state (was in Database.cs) - private bool ShowAdvanced; - private long DatabaseLastRefreshTicks; - private long DatabaseSize; - private long DatabaseLogSize; - private int DatabaseMessageCount; - - // Channel groupings shared by Cleanup-Breakdown, Retention and Export - // sections. Heading is resolved per-frame so a runtime LanguageChanged - // call updates the labels immediately. 1:1 from Privacy.cs Groups. - private static readonly (Func Heading, ChatType[] Types)[] Groups = - [ - ( - () => HellionStrings.Privacy_Group_DirectMessages, - [ChatType.TellIncoming, ChatType.TellOutgoing] - ), - ( - () => HellionStrings.Privacy_Group_PartyAlliance, - [ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam] - ), - ( - () => HellionStrings.Privacy_Group_FreeCompany, - [ - ChatType.FreeCompany, - ChatType.FreeCompanyAnnouncement, - ChatType.FreeCompanyLoginLogout, - ] - ), - ( - () => HellionStrings.Privacy_Group_Linkshells, - [ - ChatType.Linkshell1, - ChatType.Linkshell2, - ChatType.Linkshell3, - ChatType.Linkshell4, - ChatType.Linkshell5, - ChatType.Linkshell6, - ChatType.Linkshell7, - ChatType.Linkshell8, - ] - ), - ( - () => HellionStrings.Privacy_Group_CrossLinkshells, - [ - ChatType.CrossLinkshell1, - ChatType.CrossLinkshell2, - ChatType.CrossLinkshell3, - ChatType.CrossLinkshell4, - ChatType.CrossLinkshell5, - ChatType.CrossLinkshell6, - ChatType.CrossLinkshell7, - ChatType.CrossLinkshell8, - ] - ), - ( - () => HellionStrings.Privacy_Group_ExtraChat, - [ - ChatType.ExtraChatLinkshell1, - ChatType.ExtraChatLinkshell2, - ChatType.ExtraChatLinkshell3, - ChatType.ExtraChatLinkshell4, - ChatType.ExtraChatLinkshell5, - ChatType.ExtraChatLinkshell6, - ChatType.ExtraChatLinkshell7, - ChatType.ExtraChatLinkshell8, - ] - ), - ( - () => HellionStrings.Privacy_Group_PublicChat, - [ - ChatType.Say, - ChatType.Shout, - ChatType.Yell, - ChatType.NoviceNetwork, - ChatType.CustomEmote, - ChatType.StandardEmote, - ] - ), - ( - () => HellionStrings.Privacy_Group_SystemLogs, - [ - ChatType.System, - ChatType.Notice, - ChatType.Urgent, - ChatType.Echo, - ChatType.NpcDialogue, - ChatType.NpcAnnouncement, - ChatType.LootNotice, - ChatType.LootRoll, - ChatType.RetainerSale, - ChatType.Crafting, - ChatType.Gathering, - ChatType.Sign, - ChatType.RandomNumber, - ] - ), - ]; - - internal DataAndPrivacy(Plugin plugin, Configuration mutable, ILogger logger) - { - Plugin = plugin; - Mutable = mutable; - _logger = logger; - } - - public void Draw(bool sectionJustEntered) - { - // Shift-on-open keeps the Advanced tools available without a permanent - // toggle in the UI, mirroring upstream Chat 2 behaviour. - if (sectionJustEntered) - ShowAdvanced = ImGui.GetIO().KeyShift; - - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - DrawPrivacyFilterSection(); - ImGui.Spacing(); - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - DrawStorageSection(); - ImGui.Spacing(); - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - DrawRetentionSection(); - ImGui.Spacing(); - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - DrawCleanupSection(); - ImGui.Spacing(); - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - DrawExportSection(); - ImGui.Spacing(); - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - DrawDatabaseSection(); - } - - private void DrawPrivacyFilterSection() - { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_PrivacyFilter); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - // Wizard re-open sits outside the disabled block so it is always clickable. - if (ImGui.Button(HellionStrings.Wizard_Reopen_Button)) - Plugin.FirstRunWizard.IsOpen = true; - ImGui.Spacing(); - - ImGuiUtil.OptionCheckbox( - ref Mutable.PrivacyFilterEnabled, - HellionStrings.Privacy_FilterEnabled_Name, - HellionStrings.Privacy_FilterEnabled_Description - ); - ImGuiUtil.HelpMarker(HellionStrings.Privacy_FilterEnabled_StorageOnly_Help); - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - // Whitelist, presets, and PersistUnknown are greyed (still visible) - // when the filter is off — ImRaii.Disabled block preserved verbatim. - using (ImRaii.Disabled(!Mutable.PrivacyFilterEnabled)) - { - ImGuiUtil.HelpText(HellionStrings.Privacy_Whitelist_Help); - - ImGui.Spacing(); - - if (ImGui.Button(HellionStrings.Privacy_Preset_PrivacyFirst)) - Mutable.PrivacyPersistChannels = [.. PrivacyDefaults.PrivacyFirstWhitelist]; - - ImGui.SameLine(); - if (ImGui.Button(HellionStrings.Privacy_Preset_ClearAll)) - Mutable.PrivacyPersistChannels.Clear(); - - ImGui.SameLine(); - if (ImGui.Button(HellionStrings.Privacy_Preset_SelectAll)) - foreach (var group in Groups) - foreach (var t in group.Types) - Mutable.PrivacyPersistChannels.Add(t); - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - foreach (var (heading, types) in Groups) - { - using var groupTree = ImRaii.TreeNode(heading()); - if (!groupTree.Success) - continue; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - foreach (var type in types) - { - var enabled = Mutable.PrivacyPersistChannels.Contains(type); - var label = type.ToString(); - if (ImGui.Checkbox($"{label}##privacy-{(int)type}", ref enabled)) - { - if (enabled) - Mutable.PrivacyPersistChannels.Add(type); - else - Mutable.PrivacyPersistChannels.Remove(type); - } - } - } - } - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - ImGuiUtil.OptionCheckbox( - ref Mutable.PrivacyPersistUnknownChannels, - HellionStrings.Privacy_PersistUnknown_Name, - HellionStrings.Privacy_PersistUnknown_Description - ); - } - } - } - - private void DrawStorageSection() - { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Storage); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox( - Language.Options_DatabaseBattleMessages_Name, - ref Mutable.DatabaseBattleMessages - ); - ImGuiUtil.HelpMarker(Language.Options_DatabaseBattleMessages_Description); - - if ( - ImGui.Checkbox( - Language.Options_LoadPreviousSession_Name, - ref Mutable.LoadPreviousSession - ) - ) - if (Mutable.LoadPreviousSession) - Mutable.FilterIncludePreviousSessions = true; - ImGuiUtil.HelpMarker(Language.Options_LoadPreviousSession_Description); - - if ( - ImGui.Checkbox( - Language.Options_FilterIncludePreviousSessions_Name, - ref Mutable.FilterIncludePreviousSessions - ) - ) - if (!Mutable.FilterIncludePreviousSessions) - Mutable.LoadPreviousSession = false; - ImGuiUtil.HelpMarker(Language.Options_FilterIncludePreviousSessions_Description); - - var old = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat.db")); - var migratedOld = new FileInfo( - Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat-litedb.db") - ); - if (old.Exists || migratedOld.Exists) - { - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - ImGui.TextUnformatted(Language.Options_Database_Old_Heading); - ImGui.Spacing(); - - if ( - ImGuiUtil.CtrlShiftButton( - Language.Options_Database_Old_Delete, - Language.Options_Database_Old_Delete_Tooltip - ) - ) - { - try - { - if (old.Exists) - old.Delete(); - if (migratedOld.Exists) - migratedOld.Delete(); - WrapperUtil.AddNotification( - Language.Options_Database_Old_Delete_Success, - NotificationType.Success - ); - } - catch (Exception e) - { - _logger.LogError(e, "Unable to delete old database"); - WrapperUtil.AddNotification( - Language.Options_Database_Old_Delete_Error, - NotificationType.Error - ); - } - } - } - } - } - - private void DrawRetentionSection() - { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Retention); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGuiUtil.OptionCheckbox( - ref Mutable.RetentionEnabled, - HellionStrings.Retention_Enabled_Name, - HellionStrings.Retention_Enabled_Description - ); - - using (ImRaii.Disabled(!Mutable.RetentionEnabled)) - { - ImGui.Spacing(); - - var defaultDays = Mutable.RetentionDefaultDays; - if (ImGui.InputInt(HellionStrings.Retention_Default_Label, ref defaultDays)) - Mutable.RetentionDefaultDays = Math.Max(0, defaultDays); - ImGuiUtil.HelpMarker(HellionStrings.Retention_Default_Help); - - ImGui.Spacing(); - - if (ImGui.Button(HellionStrings.Retention_Reset_Spec)) - { - Mutable.RetentionPerChannelDays = - PrivacyDefaults.DefaultRetentionDays.ToDictionary(p => p.Key, p => p.Value); - } - ImGui.SameLine(); - if (ImGui.Button(HellionStrings.Retention_Clear_Overrides)) - Mutable.RetentionPerChannelDays.Clear(); - - ImGui.Spacing(); - - using (var perChannelTree = ImRaii.TreeNode(HellionStrings.Retention_Tree_Heading)) - { - if (perChannelTree.Success) - { - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - foreach (var (heading, types) in Groups) - { - using var subTree = ImRaii.TreeNode(heading()); - if (!subTree.Success) - continue; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - foreach (var type in types) - { - var hasOverride = - Mutable.RetentionPerChannelDays.TryGetValue( - type, - out var days - ); - var hasSpecDefault = - PrivacyDefaults.DefaultRetentionDays.TryGetValue( - type, - out var specDays - ); - if (!hasOverride) - days = hasSpecDefault - ? specDays - : Mutable.RetentionDefaultDays; - - var tag = - hasOverride ? HellionStrings.Retention_Tag_Override - : hasSpecDefault ? HellionStrings.Retention_Tag_Spec - : HellionStrings.Retention_Tag_Global; - if ( - ImGui.InputInt( - $"{type} {tag}##retention-{(int)type}", - ref days - ) - ) - { - days = Math.Max(0, days); - Mutable.RetentionPerChannelDays[type] = days; - } - - if (hasOverride) - { - ImGui.SameLine(); - if ( - ImGui.Button( - $"{HellionStrings.Retention_Reset_Button}##retention-reset-{(int)type}" - ) - ) - Mutable.RetentionPerChannelDays.Remove(type); - } - } - } - } - } - - ImGui.Spacing(); - - ImGuiUtil.HelpText(HellionStrings.Retention_Help_SavedNote); - ImGui.Spacing(); - - using (ImRaii.Disabled(RetentionRunning)) - { - if ( - ImGuiUtil.CtrlShiftButton( - HellionStrings.Retention_Apply_Label, - HellionStrings.Retention_Apply_Tooltip - ) - ) - StartRetentionRun(); - } - - if (RetentionRunning) - ImGuiUtil.HelpText(HellionStrings.Retention_Running); - - ImGui.Spacing(); - var lastRun = Plugin.Config.RetentionLastRunAt; - ImGuiUtil.HelpText( - lastRun == DateTimeOffset.MinValue - ? HellionStrings.Retention_LastRun_Never - : string.Format(HellionStrings.Retention_LastRun_At, lastRun.ToLocalTime()) - ); - } - } - } - - private void StartRetentionRun() - { - lock (Plugin.RetentionSweepLock) - { - if (Plugin.RetentionSweepRunning) - return; - Plugin.RetentionSweepRunning = true; - } - - var policy = Plugin.Config.RetentionPerChannelDays.ToDictionary( - p => (int)(ushort)p.Key, - p => p.Value - ); - var defaultDays = Plugin.Config.RetentionDefaultDays; - - new Thread(() => - { - try - { - var deleted = Plugin.MessageManager.Store.DeleteByRetentionPolicy( - policy, - defaultDays - ); - Plugin.Config.RetentionLastRunAt = DateTimeOffset.UtcNow; - Plugin.SaveConfig(); - - _logger.LogInformation($"Manual retention run deleted {deleted} expired messages."); - - if (deleted > 0) - { - if ( - !Plugin - .Framework.Run(() => - { - Plugin.MessageManager.ClearAllTabs(); - Plugin.MessageManager.FilterAllTabsAsync(); - }) - .Wait(TimeSpan.FromSeconds(5)) - ) - { - _logger.LogWarning( - "Retention sweep: framework refresh timed out after 5s." - ); - } - } - - WrapperUtil.AddNotification( - string.Format(HellionStrings.Retention_Success, deleted), - NotificationType.Success - ); - } - catch (Exception e) - { - _logger.LogError(e, "Manual retention run failed"); - WrapperUtil.AddNotification(HellionStrings.Retention_Error, NotificationType.Error); - } - finally - { - lock (Plugin.RetentionSweepLock) - Plugin.RetentionSweepRunning = false; - } - }) - { - IsBackground = true, - }.Start(); - } - - private void DrawCleanupSection() - { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Cleanup); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGuiUtil.HelpText(HellionStrings.Cleanup_Help_Intro); - ImGuiUtil.HelpText(HellionStrings.Cleanup_Help_SavedNote); - - ImGui.Spacing(); - - if ( - CleanupPreviewSnapshot is not null - && !CleanupPreviewSnapshot.SetEquals(Mutable.PrivacyPersistChannels) - ) - { - CleanupPreviewStale = true; - } - - using ( - var emphasis = CleanupPreviewStale - ? ImRaii.PushColor(ImGuiCol.Button, ImGuiColors.HealerGreen with { W = 0.6f }) - : null - ) - using (ImRaii.Disabled(CleanupRunning)) - { - if (ImGui.Button(HellionStrings.Cleanup_RefreshPreview)) - RefreshCleanupPreview(); - } - - if (CleanupCounts is null) - { - ImGuiUtil.HelpText(HellionStrings.Cleanup_NoPreview); - return; - } - - if (CleanupPreviewStale) - { - ImGui.Spacing(); - ImGuiUtil.HelpText(HellionStrings.Cleanup_Preview_Stale); - } - - ImGui.Spacing(); - - using ( - var staleColor = CleanupPreviewStale - ? ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey) - : null - ) - { - ImGuiUtil.HelpText( - string.Format( - HellionStrings.Cleanup_TotalStored, - CleanupKeepCount + CleanupDeleteCount - ) - ); - ImGuiUtil.HelpText( - string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount) - ); - ImGuiUtil.HelpText( - string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount) - ); - } - - using (var breakdownTree = ImRaii.TreeNode(HellionStrings.Cleanup_Breakdown)) - { - if (breakdownTree.Success) - { - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - foreach ( - var (chatType, count) in CleanupCounts.OrderByDescending(p => p.Value) - ) - { - var name = Enum.IsDefined(typeof(ChatType), (ushort)chatType) - ? ((ChatType)(ushort)chatType).ToString() - : $"Unknown({chatType})"; - var keeps = WouldBeKept(chatType); - var marker = keeps - ? HellionStrings.Cleanup_Marker_Keep - : HellionStrings.Cleanup_Marker_Delete; - ImGuiUtil.HelpText($"{marker} {name} — {count:N0}"); - } - } - } - - ImGui.Spacing(); - - using (ImRaii.Disabled(CleanupRunning || CleanupDeleteCount == 0)) - { - if ( - ImGuiUtil.CtrlShiftButton( - HellionStrings.Cleanup_Apply_Label, - string.Format(HellionStrings.Cleanup_Apply_Tooltip, CleanupDeleteCount) - ) - ) - StartCleanup(); - } - - if (CleanupRunning) - ImGuiUtil.HelpText(HellionStrings.Cleanup_Running); - } - } - - private bool WouldBeKept(int chatType) - { - if (!Plugin.Config.PrivacyFilterEnabled) - return true; - if (Plugin.Config.PrivacyPersistChannels.Contains((ChatType)(ushort)chatType)) - return true; - return Plugin.Config.PrivacyPersistUnknownChannels; - } - - private void RefreshCleanupPreview() - { - try - { - CleanupCounts = Plugin.MessageManager.Store.GetMessageCountsByChatType(); - CleanupKeepCount = 0; - CleanupDeleteCount = 0; - foreach (var (chatType, count) in CleanupCounts) - { - if (WouldBeKept(chatType)) - CleanupKeepCount += count; - else - CleanupDeleteCount += count; - } - - CleanupPreviewSnapshot = new HashSet(Mutable.PrivacyPersistChannels); - CleanupPreviewStale = false; - } - catch (Exception e) - { - _logger.LogError(e, "Failed to compute cleanup preview"); - WrapperUtil.AddNotification( - HellionStrings.Cleanup_PreviewError, - NotificationType.Error - ); - } - } - - private void StartCleanup() - { - if (CleanupRunning) - return; - - CleanupRunning = true; - var allowed = Plugin.Config.PrivacyPersistChannels.Select(t => (int)(ushort)t).ToList(); - - var thread = new Thread(() => - { - try - { - var deleted = Plugin.MessageManager.Store.CleanupRetainOnly(allowed); - _logger.LogInformation($"Privacy cleanup: deleted {deleted} messages"); - - if ( - !Plugin - .Framework.Run(() => - { - Plugin.MessageManager.ClearAllTabs(); - Plugin.MessageManager.FilterAllTabs(); - }) - .Wait(TimeSpan.FromSeconds(5)) - ) - { - _logger.LogWarning("Privacy cleanup: framework refresh timed out after 5s."); - } - - WrapperUtil.AddNotification( - string.Format(HellionStrings.Cleanup_Success, deleted), - NotificationType.Success - ); - } - catch (Exception e) - { - _logger.LogError(e, "Privacy cleanup failed"); - WrapperUtil.AddNotification(HellionStrings.Cleanup_Error, NotificationType.Error); - } - finally - { - CleanupRunning = false; - CleanupCounts = null; - } - }); - thread.IsBackground = true; - thread.Start(); - } - - private void DrawExportSection() - { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Export); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGuiUtil.HelpText(HellionStrings.Export_Help); - - ImGui.Spacing(); - - if (ImGui.InputInt(HellionStrings.Export_Range_Label, ref ExportRangeDays)) - ExportRangeDays = Math.Max(0, ExportRangeDays); - - ImGui.InputText(HellionStrings.Export_Sender_Label, ref ExportSenderSubstring, 256); - - using (var channelsTree = ImRaii.TreeNode(HellionStrings.Export_Channels_Heading)) - { - if (channelsTree.Success) - { - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGuiUtil.HelpText(HellionStrings.Export_Channels_AllOff); - foreach (var (heading, types) in Groups) - { - using var subTree = ImRaii.TreeNode( - $"{heading()}##export-group-{heading()}" - ); - if (!subTree.Success) - continue; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - foreach (var type in types) - { - var enabled = ExportSelectedChannels.Contains(type); - if (ImGui.Checkbox($"{type}##export-{(int)type}", ref enabled)) - { - if (enabled) - ExportSelectedChannels.Add(type); - else - ExportSelectedChannels.Remove(type); - } - } - } - } - } - } - - ImGui.Spacing(); - ImGui.TextUnformatted(HellionStrings.Export_Format_Label); - ImGui.SameLine(); - var fmt = (int)ExportFormat; - if ( - ImGui.RadioButton( - HellionStrings.Export_Format_Markdown, - ref fmt, - (int)ExportFormat.Markdown - ) - ) - ExportFormat = ExportFormat.Markdown; - ImGui.SameLine(); - if ( - ImGui.RadioButton( - HellionStrings.Export_Format_Json, - ref fmt, - (int)ExportFormat.Json - ) - ) - ExportFormat = ExportFormat.Json; - ImGui.SameLine(); - if (ImGui.RadioButton(HellionStrings.Export_Format_Csv, ref fmt, (int)ExportFormat.Csv)) - ExportFormat = ExportFormat.Csv; - - ImGui.Spacing(); - - using (ImRaii.Disabled(ExportRunning)) - { - if (ImGui.Button(HellionStrings.Export_Button)) - PromptExport(); - } - - if (ExportRunning) - ImGuiUtil.HelpText(HellionStrings.Export_Running); - } - } - - private void PromptExport() - { - var defaultName = $"hellion-chat-export-{DateTimeOffset.Now:yyyyMMdd-HHmm}"; - var ext = ExportFormat.Extension(); - - Plugin.FileDialogManager.SaveFileDialog( - HellionStrings.Export_Dialog_Title, - ExportFormat.Filter(), - defaultName, - ext, - (success, path) => - { - if (!success || string.IsNullOrWhiteSpace(path)) - return; - StartExport(path); - } - ); - } - - private void StartExport(string path) - { - if (ExportRunning) - return; - ExportRunning = true; - - var types = - ExportSelectedChannels.Count > 0 - ? ExportSelectedChannels.Select(t => (int)(ushort)t).ToList() - : null; - - DateTimeOffset? from = - ExportRangeDays > 0 ? DateTimeOffset.UtcNow.AddDays(-ExportRangeDays) : null; - - var senderSubstring = string.IsNullOrWhiteSpace(ExportSenderSubstring) - ? null - : ExportSenderSubstring.Trim(); - var format = ExportFormat; - var filterDesc = new MessageExporter.FilterDescription(types, from, null, senderSubstring); - - new Thread(() => - { - try - { - using var enumerator = Plugin.MessageManager.Store.StreamForExport( - types, - from, - null - ); - var written = MessageExporter.ExportToFile(path, format, enumerator, filterDesc); - - if (written > 0) - WrapperUtil.AddNotification( - string.Format(HellionStrings.Export_Success, written, path), - NotificationType.Success - ); - else - WrapperUtil.AddNotification(HellionStrings.Export_Empty, NotificationType.Info); - } - catch (Exception e) - { - _logger.LogError(e, "Export failed"); - WrapperUtil.AddNotification(HellionStrings.Export_Error, NotificationType.Error); - } - finally - { - ExportRunning = false; - } - }) - { - IsBackground = true, - }.Start(); - } - - private void DrawDatabaseSection() - { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Database); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - if (DatabaseLastRefreshTicks + 5 * 1000 < Environment.TickCount64) - { - DatabaseSize = Plugin.MessageManager.Store.DatabaseSize(); - DatabaseLogSize = Plugin.MessageManager.Store.DatabaseLogSize(); - DatabaseMessageCount = Plugin.MessageManager.Store.MessageCount(); - DatabaseLastRefreshTicks = Environment.TickCount64; - } - - ImGuiUtil.HelpText( - string.Format( - Language.Options_Database_Metadata_Path, - MessageManager.DatabasePath() - ) - ); - if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) - { - var path = Path.GetDirectoryName(MessageManager.DatabasePath()); - ImGui.SetClipboardText(path); - WrapperUtil.AddNotification( - Language.Options_Database_Metadata_CopyConfigPathNotification, - NotificationType.Info - ); - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - ImGuiUtil.Tooltip(Language.Options_Database_Metadata_CopyConfigPath); - } - - ImGuiUtil.HelpText( - string.Format( - Language.Options_Database_Metadata_Size, - StringUtil.BytesToString(DatabaseSize) - ) - ); - if (ImGui.IsItemHovered()) - ImGuiUtil.Tooltip(StringUtil.BytesToString(DatabaseSize)); - - ImGuiUtil.HelpText( - string.Format( - Language.Options_Database_Metadata_LogSize, - StringUtil.BytesToString(DatabaseLogSize) - ) - ); - if (ImGui.IsItemHovered()) - ImGuiUtil.Tooltip(StringUtil.BytesToString(DatabaseLogSize)); - - ImGuiUtil.HelpText( - string.Format(Language.Options_Database_Metadata_MessageCount, DatabaseMessageCount) - ); - - if ( - ImGuiUtil.CtrlShiftButton( - Language.Options_ClearDatabase_Button, - Language.Options_ClearDatabase_Tooltip - ) - ) - { - _logger.LogWarning("Clearing messages from database"); - Plugin.MessageManager.Store.ClearMessages(); - Plugin.MessageManager.ClearAllTabs(); - - DatabaseLastRefreshTicks = 0; - WrapperUtil.AddNotification( - Language.Options_ClearDatabase_Success, - NotificationType.Info - ); - } - - // Advanced sub-block: only visible when the tab was opened with Shift held. - // Gate matches the Shift-on-open flag set at the top of Draw(). - if (!ShowAdvanced) - return; - - ImGui.Spacing(); - using var advTree = ImRaii.TreeNode( - HellionStrings.Settings_DataManagement_Advanced_Heading - ); - if (!advTree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - using var wrap = ImRaii.TextWrapPos(0.0f); - - ImGuiUtil.WarningText(Language.Options_Database_Advanced_Warning); - if ( - ImGuiUtil.CtrlShiftButton( - "Perform maintenance", - "Ctrl+Shift: MessageManager.Store.PerformMaintenance()" - ) - ) - Plugin.MessageManager.Store.PerformMaintenance(); - - if ( - ImGuiUtil.CtrlShiftButton( - "Reload messages from database", - "Ctrl+Shift: MessageManager.FilterAllTabs()" - ) - ) - { - Plugin.MessageManager.ClearAllTabs(); - Plugin.MessageManager.FilterAllTabsAsync(); - } - - if ( - ImGuiUtil.CtrlShiftButton( - "Inject 10,000 messages", - "Ctrl+Shift: creates 10,000 unique messages (async)" - ) - ) - new Thread(() => InsertMessages(10_000)).Start(); - } - } - } - - private void InsertMessages(int count) - { - _logger.LogInformation($"Inserting {count} messages due to user request"); - - var stopwatch = Stopwatch.StartNew(); - var playerName = Plugin.PlayerState.CharacterName; - var worldId = Plugin.PlayerState.HomeWorld.ValueNullable?.RowId ?? 0; - var senderSource = new SeStringBuilder() - .AddText("<") - .Add(new PlayerPayload(playerName, worldId)) - .AddText("Random Message") - .Add(RawPayload.LinkTerminator) - .AddText(">: ") - .Build(); - var senderChunks = ChunkUtil - .ToChunks(senderSource, ChunkSource.Sender, ChatType.Debug) - .ToList(); - var messages = new List(count); - for (var i = 0; i < count; i++) - { - var contentSource = new SeStringBuilder() - .AddText("Random message payload - ") - .AddItalics(Guid.NewGuid().ToString()) - .Build(); - var contentChunks = ChunkUtil - .ToChunks(contentSource, ChunkSource.Content, ChatType.Debug) - .ToList(); - - var chatCode = new ChatCode(XivChatType.Say, 0, 0); - messages.Add( - new Message( - Guid.NewGuid(), - Plugin.MessageManager.CurrentContentId, - Plugin.MessageManager.CurrentContentId, - DateTimeOffset.UtcNow, - chatCode, - senderChunks, - contentChunks, - senderSource, - contentSource, - Guid.Empty - ) - ); - } - - var elapsedTicks = stopwatch.ElapsedTicks; - stopwatch.Stop(); - _logger.LogInformation( - $"Crafted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)" - ); - - stopwatch = Stopwatch.StartNew(); - foreach (var message in messages) - Plugin.MessageManager.Store.UpsertMessage(message); - - elapsedTicks = stopwatch.ElapsedTicks; - stopwatch.Stop(); - _logger.LogInformation( - $"Upserted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)" - ); - - Plugin - .Framework.Run(() => - { - stopwatch = Stopwatch.StartNew(); - Plugin.MessageManager.ClearAllTabs(); - elapsedTicks = stopwatch.ElapsedTicks; - stopwatch.Stop(); - _logger.LogInformation( - $"Cleared {Plugin.Config.Tabs.Count} tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)" - ); - }) - .Wait(); - - Plugin - .Framework.Run(() => - { - stopwatch = Stopwatch.StartNew(); - Plugin.MessageManager.FilterAllTabs(); - elapsedTicks = stopwatch.ElapsedTicks; - stopwatch.Stop(); - _logger.LogInformation( - $"Fetched and filtered all tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)" - ); - }) - .Wait(); - } -} diff --git a/HellionChat/Ui/SettingsTabs/General.cs b/HellionChat/Ui/SettingsTabs/General.cs deleted file mode 100644 index 927be98..0000000 --- a/HellionChat/Ui/SettingsTabs/General.cs +++ /dev/null @@ -1,217 +0,0 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface.Utility; -using Dalamud.Interface.Utility.Raii; -using HellionChat.Resources; -using HellionChat.Util; - -namespace HellionChat.Ui.SettingsTabs; - -internal sealed class General : ISettingsTab -{ - private Plugin Plugin { get; } - private Configuration Mutable { get; } - - public string Name => HellionStrings.Settings_Tab_General + "###tabs-general"; - - internal General(Plugin plugin, Configuration mutable) - { - Plugin = plugin; - Mutable = mutable; - } - - public void Draw(bool sectionJustEntered) - { - DrawInputSection(sectionJustEntered); - ImGui.Spacing(); - DrawSoundSection(sectionJustEntered); - ImGui.Spacing(); - DrawLanguageSection(sectionJustEntered); - ImGui.Spacing(); - DrawPerformanceSection(sectionJustEntered); - } - - private void DrawInputSection(bool sectionJustEntered) - { - // Collapse every time the tab is freshly entered so state doesn't bleed across sessions. - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Input); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_KeepInputFocus_Name, ref Mutable.KeepInputFocus); - ImGuiUtil.HelpMarker(Language.Options_KeepInputFocus_Description); - - ImGui.Spacing(); - ImGui.TextUnformatted(Language.Options_ChatTabForwardKeybind_Name); - ImGui.SetNextItemWidth(-1); - ImGuiUtil.KeybindInput("ChatTabForwardKeybind", ref Mutable.ChatTabForward); - - ImGui.TextUnformatted(Language.Options_ChatTabBackwardKeybind_Name); - ImGui.SetNextItemWidth(-1); - ImGuiUtil.KeybindInput("ChatTabBackwardKeybind", ref Mutable.ChatTabBackward); - - ImGui.Spacing(); - - using ( - var combo = ImGuiUtil.BeginComboVertical( - Language.Options_KeybindMode_Name, - Mutable.KeybindMode.Name() - ) - ) - { - if (combo.Success) - { - foreach (var mode in Enum.GetValues()) - { - if (ImGui.Selectable(mode.Name(), Mutable.KeybindMode == mode)) - { - Mutable.KeybindMode = mode; - } - - if (ImGui.IsItemHovered()) - { - ImGuiUtil.Tooltip(mode.Tooltip() ?? ""); - } - } - } - } - ImGuiUtil.HelpMarker( - string.Format(Language.Options_KeybindMode_Description, Plugin.PluginName) - ); - } - } - - private void DrawSoundSection(bool sectionJustEntered) - { - // Collapse every time the tab is freshly entered so state doesn't bleed across sessions. - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Sound); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_PlaySounds_Name, ref Mutable.PlaySounds); - ImGuiUtil.HelpMarker(Language.Options_PlaySounds_Description); - // Volume is stored as a 0-1 float but shown as 0-100% to match user - // intuition. Full range — unlike opacity there is no unsafe floor. - var customSoundVolumePercent = Mutable.CustomSoundVolume * 100f; - if ( - ImGuiUtil.DragFloatVertical( - HellionStrings.Settings_General_CustomSoundVolume_Name, - ref customSoundVolumePercent, - 1f, - 0f, - 100f, - $"{customSoundVolumePercent:N0}%%", - ImGuiSliderFlags.AlwaysClamp - ) - ) - { - Mutable.CustomSoundVolume = customSoundVolumePercent / 100f; - } - // Show the functional description and the per-tab navigation hint together. - ImGuiUtil.HelpMarker( - HellionStrings.Settings_General_CustomSoundVolume_Description - + "\n\n" - + HellionStrings.Settings_Section_Sound_TabsHint - ); - } - } - - private void DrawLanguageSection(bool sectionJustEntered) - { - // Collapse every time the tab is freshly entered so state doesn't bleed across sessions. - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Language); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_SortAutoTranslate_Name, ref Mutable.SortAutoTranslate); - ImGuiUtil.HelpMarker(Language.Options_SortAutoTranslate_Description); - - ImGui.Spacing(); - - using ( - var combo = ImGuiUtil.BeginComboVertical( - Language.Options_Language_Name, - Mutable.LanguageOverride.Name() - ) - ) - { - if (combo.Success) - { - // None pinned first, then alphabetical by endonym so source order - // (append-only for serialisation safety) is not visible to users. - var sortedLanguages = Enum.GetValues() - .OrderBy(l => l == LanguageOverride.None ? 0 : 1) - .ThenBy(l => l.Name(), StringComparer.InvariantCulture); - foreach (var language in sortedLanguages) - { - if (ImGui.Selectable(language.Name())) - { - Mutable.LanguageOverride = language; - } - } - } - } - ImGuiUtil.HelpMarker( - string.Format(Language.Options_Language_Description, Plugin.PluginName) - ); - // v1.5.3: HellionChat's font stack covers 24 languages but FFXIV's - // engine only supports EN/DE/FR/JA for chat input/sending. - ImGuiUtil.WarningText(HellionStrings.Settings_Language_FFXIVCoverage_Warning); - ImGui.Spacing(); - - using ( - var combo = ImGuiUtil.BeginComboVertical( - Language.Options_CommandHelpSide_Name, - Mutable.CommandHelpSide.Name() - ) - ) - { - if (combo.Success) - { - foreach (var side in Enum.GetValues()) - { - if (ImGui.Selectable(side.Name(), Mutable.CommandHelpSide == side)) - { - Mutable.CommandHelpSide = side; - } - } - } - } - ImGuiUtil.HelpMarker( - string.Format(Language.Options_CommandHelpSide_Description, Plugin.PluginName) - ); - ImGui.Spacing(); - } - } - - private void DrawPerformanceSection(bool sectionJustEntered) - { - // Collapse every time the tab is freshly entered so state doesn't bleed across sessions. - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Performance); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt(Language.Options_MaxLinesToShow_Name, ref Mutable.MaxLinesToRender)) - { - Mutable.MaxLinesToRender = Math.Clamp(Mutable.MaxLinesToRender, 1, 10_000); - } - ImGuiUtil.HelpMarker(Language.Options_MaxLinesToShow_Description); - } - } -} diff --git a/HellionChat/Ui/SettingsTabs/ISettingsTab.cs b/HellionChat/Ui/SettingsTabs/ISettingsTab.cs deleted file mode 100755 index 9dbdd39..0000000 --- a/HellionChat/Ui/SettingsTabs/ISettingsTab.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace HellionChat.Ui.SettingsTabs; - -internal interface ISettingsTab -{ - string Name { get; } - void Draw(bool sectionJustEntered); -} diff --git a/HellionChat/Ui/SettingsTabs/Tabs.cs b/HellionChat/Ui/SettingsTabs/Tabs.cs deleted file mode 100755 index 15f2d8c..0000000 --- a/HellionChat/Ui/SettingsTabs/Tabs.cs +++ /dev/null @@ -1,601 +0,0 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Game.ClientState.Objects.SubKinds; -using Dalamud.Interface; -using Dalamud.Interface.Utility.Raii; -using FFXIVClientStructs.FFXIV.Client.UI; -using HellionChat.Code; -using HellionChat.Resources; -using HellionChat.Util; - -namespace HellionChat.Ui.SettingsTabs; - -internal sealed class Tabs : ISettingsTab -{ - private Plugin Plugin { get; } - private Configuration Mutable { get; } - - public string Name => HellionStrings.Settings_Tab_Tabs + "###tabs-tabs"; - - private int ToOpen = -2; - - internal Tabs(Plugin plugin, Configuration mutable) - { - Plugin = plugin; - Mutable = mutable; - } - - public void Draw(bool sectionJustEntered) - { - const string addTabPopup = "add-tab-popup"; - - ImGuiUtil.HelpText(HellionStrings.Tabs_Presets_Linkshell_Hint); - ImGui.Spacing(); - - if (ImGuiUtil.IconButton(FontAwesomeIcon.Plus, tooltip: Language.Options_Tabs_Add)) - ImGui.OpenPopup(addTabPopup); - - using (var popup = ImRaii.Popup(addTabPopup)) - { - if (popup) - { - if (ImGui.Selectable(Language.Options_Tabs_NewTab)) - Mutable.Tabs.Add(new Tab()); - - ImGui.Separator(); - - if ( - ImGui.Selectable( - string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_General) - ) - ) - Mutable.Tabs.Add(TabsUtil.VanillaGeneral); - - if ( - ImGui.Selectable( - string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_Event) - ) - ) - Mutable.Tabs.Add(TabsUtil.VanillaEvent); - - if ( - ImGui.Selectable( - string.Format(Language.Options_Tabs_Preset, Language.Tabs_Presets_Tell) - ) - ) - Mutable.Tabs.Add(TabsUtil.VanillaTellExclusive); - } - } - - var toRemove = -1; - var doOpens = ToOpen > -2; - for (var i = 0; i < Mutable.Tabs.Count; i++) - { - var tab = Mutable.Tabs[i]; - - // Sub-sections (Channels/Display/Notification/Input/Pop-out) are inlined into - // this loop body rather than extracted to helpers, because each one closes over - // the per-iteration `i` and `tab` state. Extraction would mean passing both - // into every helper without meaningful encapsulation gain. - - // ToOpen controls which tab-item TreeNode is open (e.g. after add/move). - // This is the outer level — not touched by sectionJustEntered. - if (doOpens) - ImGui.SetNextItemOpen(i == ToOpen); - - using var treeNode = ImRaii.TreeNode($"{tab.Name}###tab-{i}"); - if (!treeNode.Success) - continue; - - using var pushedId = ImRaii.PushId($"tab-{i}"); - - if ( - ImGuiUtil.IconButton( - FontAwesomeIcon.TrashAlt, - tooltip: Language.Options_Tabs_Delete - ) - ) - { - toRemove = i; - ToOpen = -1; - } - - ImGui.SameLine(); - - if ( - ImGuiUtil.IconButton(FontAwesomeIcon.ArrowUp, tooltip: Language.Options_Tabs_MoveUp) - && i > 0 - ) - { - (Mutable.Tabs[i - 1], Mutable.Tabs[i]) = (Mutable.Tabs[i], Mutable.Tabs[i - 1]); - ToOpen = i - 1; - } - - ImGui.SameLine(); - - if ( - ImGuiUtil.IconButton( - FontAwesomeIcon.ArrowDown, - tooltip: Language.Options_Tabs_MoveDown - ) - && i < Mutable.Tabs.Count - 1 - ) - { - (Mutable.Tabs[i + 1], Mutable.Tabs[i]) = (Mutable.Tabs[i], Mutable.Tabs[i + 1]); - ToOpen = i + 1; - } - - // Name and Icon are always visible — no sub-section collapse for these. - ImGui.InputText( - Language.Options_Tabs_Name, - ref tab.Name, - 512, - ImGuiInputTextFlags.EnterReturnsTrue - ); - - // Per-tab icon override added in v1.2.0. Falls back to default mapping if unset. - ImGui.TextUnformatted(HellionStrings.Tabs_Icon_Label); - ImGui.SameLine(); - ImGuiUtil.HelpMarker(HellionStrings.Tabs_Icon_HelpMarker); - - var iconCurrent = string.IsNullOrEmpty(tab.Icon) ? "" : tab.Icon; - var iconPreview = - iconCurrent.Length == 0 ? HellionStrings.Tabs_Icon_DefaultOption : iconCurrent; - using (var combo = ImRaii.Combo($"##icon-{i}", iconPreview)) - { - if (combo.Success) - { - // First option clears the icon and lets the default mapping take over. - if ( - ImGui.Selectable( - HellionStrings.Tabs_Icon_DefaultOption, - iconCurrent.Length == 0 - ) - ) - { - tab.Icon = null; - } - - ImGui.Separator(); - - // Options sourced from TabIconGlyphResolver.PickerOptions (single source of truth). - foreach (var option in TabIconGlyphResolver.PickerOptions) - { - var isSelected = string.Equals( - iconCurrent, - option, - StringComparison.OrdinalIgnoreCase - ); - if (ImGui.Selectable(option, isSelected)) - { - tab.Icon = option; - } - } - } - } - - ImGui.Spacing(); - - // ── Sub-section: Channels ───────────────────────────────────────── - // First because it answers "what does this tab collect?" — most important. - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using ( - var secChannels = ImRaii.TreeNode( - HellionStrings.Settings_Section_Tab_Channels + $"##sec-channels-{i}" - ) - ) - { - if (secChannels.Success) - { - using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false); - ImGuiUtil.ChannelSelector(Language.Options_Tabs_Channels, tab.SelectedChannels); - ImGuiUtil.ExtraChatSelector( - Language.Options_Tabs_ExtraChatChannels, - ref tab.ExtraChatAll, - tab.ExtraChatChannels - ); - } - } - - ImGui.Spacing(); - - // ── Sub-section: Display ────────────────────────────────────────── - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using ( - var secDisplay = ImRaii.TreeNode( - HellionStrings.Settings_Section_Tab_Display + $"##sec-display-{i}" - ) - ) - { - if (secDisplay.Success) - { - using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false); - - ImGui.Checkbox(Language.Options_Tabs_ShowTimestamps, ref tab.DisplayTimestamp); - - using ( - var combo = ImGuiUtil.BeginComboVertical( - Language.Options_Tabs_UnreadMode, - tab.UnreadMode.Name() - ) - ) - { - if (combo.Success) - { - foreach (var mode in Enum.GetValues()) - { - if (ImGui.Selectable(mode.Name(), tab.UnreadMode == mode)) - tab.UnreadMode = mode; - - if (mode.Tooltip() is { } tooltip && ImGui.IsItemHovered()) - ImGuiUtil.Tooltip(tooltip); - } - } - } - - // Only relevant when the global hide-when-inactive is on. - if (Mutable.HideWhenInactive) - ImGui.Checkbox( - Language.Options_Tabs_InactivityBehaviour, - ref tab.UnhideOnActivity - ); - } - } - - ImGui.Spacing(); - - // ── Sub-section: Notification ───────────────────────────────────── - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using ( - var secNotif = ImRaii.TreeNode( - HellionStrings.Settings_Section_Tab_Notification + $"##sec-notif-{i}" - ) - ) - { - if (secNotif.Success) - { - using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false); - - ImGui.Checkbox( - HellionStrings.Tabs_NotificationSound_Enable_Name, - ref tab.EnableNotificationSound - ); - ImGuiUtil.HelpMarker(HellionStrings.Tabs_NotificationSound_Description); - if (tab.EnableNotificationSound) - { - using var notifIndent = ImRaii.PushIndent(10.0f); - // Build a readable preview label for the currently selected sound. - var soundPreview = - tab.NotificationSoundId <= 16 - ? $"{HellionStrings.Tabs_NotificationSound_Option} {tab.NotificationSoundId}" - : $"{HellionStrings.Tabs_NotificationSound_CustomOption} {tab.NotificationSoundId - 16}"; - using (var combo = ImRaii.Combo($"##notif-sound-{i}", soundPreview)) - { - if (combo.Success) - { - for (uint s = 1; s <= 16; s++) - { - if ( - ImGui.Selectable( - $"{HellionStrings.Tabs_NotificationSound_Option} {s}", - tab.NotificationSoundId == s - ) - ) - tab.NotificationSoundId = s; - } - - ImGui.Separator(); - - // Bundled custom sounds (ids 17-19). - for (uint n = 1; n <= 3; n++) - { - var customId = 16 + n; - if ( - ImGui.Selectable( - $"{HellionStrings.Tabs_NotificationSound_CustomOption} {n}", - tab.NotificationSoundId == customId - ) - ) - tab.NotificationSoundId = customId; - } - } - } - - // Let the user hear the currently selected sound without waiting - // for a real message to arrive in this tab. - ImGui.SameLine(); - if ( - ImGuiUtil.IconButton( - FontAwesomeIcon.Play, - tooltip: HellionStrings.Tabs_NotificationSound_Preview - ) - ) - { - var previewId = tab.NotificationSoundId; - if (previewId <= 16) - { - Plugin.Framework.RunOnFrameworkThread(() => - { - unsafe - { - UIGlobals.PlaySoundEffect(previewId); - } - }); - } - else - { - Plugin.CustomAudioPlayer.Play( - (int)previewId - 16, - Mutable.CustomSoundVolume - ); - } - } - } - - // Volume is stored as a 0-1 float but shown as 0-100%. - // Same field as General → Sound; shown here for convenience. - // DragFloatVertical derives its widget ID from the label text and exposes no - // override. We inline the equivalent (text label + SetNextItemWidth + DragFloat) - // to keep an explicit ##tab-volume-{i} ID, which reads more clearly than relying - // on the surrounding PushId("tab-{i}") scope to disambiguate identical labels. - // Volume is global (Mutable.CustomSoundVolume) and applies to every tab's - // notification sound, so it is shown unconditionally — not gated by the - // per-tab EnableNotificationSound toggle. - ImGui.TextUnformatted(HellionStrings.Settings_General_CustomSoundVolume_Name); - ImGui.SetNextItemWidth(-1); - var customSoundVolumePercent = Mutable.CustomSoundVolume * 100f; - if ( - ImGui.DragFloat( - $"##tab-volume-{i}", - ref customSoundVolumePercent, - 1f, - 0f, - 100f, - $"{customSoundVolumePercent:N0}%%", - ImGuiSliderFlags.AlwaysClamp - ) - ) - { - Mutable.CustomSoundVolume = customSoundVolumePercent / 100f; - } - // Applies globally — same value as in General → Sound. - ImGuiUtil.HelpMarker( - HellionStrings.Settings_General_CustomSoundVolume_Description - + "\n\n" - + HellionStrings.Settings_Section_Tab_Volume_AllTabsHint - ); - } - } - - ImGui.Spacing(); - - // ── Sub-section: Input ──────────────────────────────────────────── - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using ( - var secInput = ImRaii.TreeNode( - HellionStrings.Settings_Section_Tab_Input + $"##sec-input-{i}" - ) - ) - { - if (secInput.Success) - { - using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false); - - ImGui.Checkbox(Language.Options_Tabs_NoInput, ref tab.InputDisabled); - if (!tab.InputDisabled) - { - var input = - tab.Channel?.ToChatType().Name() - ?? Language.Options_Tabs_NoInputChannel; - using ( - var combo = ImGuiUtil.BeginComboVertical( - Language.Options_Tabs_InputChannel, - input - ) - ) - { - if (combo.Success) - { - if ( - ImGui.Selectable( - Language.Options_Tabs_NoInputChannel, - tab.Channel == null - ) - ) - tab.Channel = null; - - foreach (var channel in Enum.GetValues()) - if ( - ImGui.Selectable( - channel.ToChatType().Name(), - tab.Channel == channel - ) - ) - tab.Channel = channel; - } - } - - var player = Plugin.ObjectTable.LocalPlayer; - if (tab.Channel == InputChannel.Tell && player != null) - { - ImGui.Checkbox( - Language.Options_Tabs_SenderMessages, - ref tab.AllSenderMessages - ); - ImGuiUtil.HelpText(Language.Options_Help_SenderMessages); - - var worlds = Sheets - .WorldsOnDatacenter(player) - .OrderByDescending(world => world.DataCenter.RowId) - .ThenBy(world => world.Name.ToString()) - .ToList(); - - using (ImRaii.ItemWidth(ImGui.GetWindowWidth() / 3f)) - { - ImGui.Text(Language.Options_Header_Target); - ImGui.SameLine(); - - var name = tab.TellTarget.Name; - if (ImGui.InputText("##targetInput", ref name, 21)) - tab.TellTarget.Name = name; - - ImGui.SameLine(); - - // Guard against an empty worlds list (character switch or sheet not yet populated) - // to avoid an out-of-bounds crash on worlds[selectedWorld]. - if (worlds.Count == 0) - { - ImGui.TextDisabled("(no worlds available)"); - } - else - { - var selectedWorld = worlds.FindIndex(world => - world.RowId == tab.TellTarget.World - ); - if (selectedWorld == -1) - selectedWorld = 0; - - using ( - var combo = ImRaii.Combo( - "###player-world", - worlds[selectedWorld].Name.ToString() - ) - ) - { - if (combo.Success) - { - var lastDc = worlds.First().DataCenter.RowId; - foreach (var (idx, world) in worlds.Index()) - { - if ( - ImGui.Selectable( - world.Name.ToString(), - selectedWorld == idx - ) - ) - { - selectedWorld = idx; - tab.TellTarget.World = worlds[ - selectedWorld - ].RowId; - } - - if (lastDc == world.DataCenter.RowId) - continue; - - lastDc = world.DataCenter.RowId; - ImGui.Separator(); - } - } - } - } - } - - var target = - (Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target) - as IPlayerCharacter; - using (ImRaii.Disabled(target == null)) - { - if (ImGui.Button("Set to target") && target != null) - tab.TellTarget.FromTarget(target); - } - } - } - } - } - - ImGui.Spacing(); - - // ── Sub-section: Pop-out window ─────────────────────────────────── - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using ( - var secPopOut = ImRaii.TreeNode( - HellionStrings.Settings_Section_Tab_PopOut + $"##sec-popout-{i}" - ) - ) - { - if (secPopOut.Success) - { - using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false); - - ImGui.Checkbox(Language.Options_Tabs_PopOut, ref tab.PopOut); - if (tab.PopOut) - { - using var _ = ImRaii.PushIndent(10.0f); - ImGui.Checkbox( - Language.Options_Tabs_IndependentOpacity, - ref tab.IndependentOpacity - ); - if (tab.IndependentOpacity) - ImGuiUtil.DragFloatVertical( - Language.Options_Tabs_Opacity, - ref tab.Opacity, - 0.25f, - 0f, - 100f, - $"{tab.Opacity:N2}%%", - ImGuiSliderFlags.AlwaysClamp - ); - - ImGui.Checkbox( - Language.Options_Tabs_IndependentHide, - ref tab.IndependentHide - ); - if (tab.IndependentHide) - { - using var __ = ImRaii.PushIndent(10.0f); - ImGuiUtil.OptionCheckbox( - ref tab.HideDuringCutscenes, - Language.Options_HideDuringCutscenes_Name - ); - ImGui.Spacing(); - - ImGuiUtil.OptionCheckbox( - ref tab.HideWhenNotLoggedIn, - Language.Options_HideWhenNotLoggedIn_Name - ); - ImGui.Spacing(); - - ImGuiUtil.OptionCheckbox( - ref tab.HideWhenUiHidden, - Language.Options_HideWhenUiHidden_Name - ); - ImGui.Spacing(); - - ImGuiUtil.OptionCheckbox( - ref tab.HideInLoadingScreens, - Language.Options_HideInLoadingScreens_Name - ); - ImGui.Spacing(); - - ImGuiUtil.OptionCheckbox( - ref tab.HideInBattle, - Language.Options_HideInBattle_Name - ); - ImGui.Spacing(); - } - - ImGuiUtil.OptionCheckbox(ref tab.CanMove, Language.Popout_CanMove_Name); - ImGui.Spacing(); - - ImGuiUtil.OptionCheckbox(ref tab.CanResize, Language.Popout_CanResize_Name); - ImGui.Spacing(); - } - } - } - } - - if (toRemove > -1) - { - Mutable.Tabs.RemoveAt(toRemove); - Plugin.WantedTab = 0; - } - - if (doOpens) - ToOpen = -2; - } -} diff --git a/HellionChat/Ui/SettingsTabs/ThemeMockup.cs b/HellionChat/Ui/SettingsTabs/ThemeMockup.cs deleted file mode 100644 index f81798f..0000000 --- a/HellionChat/Ui/SettingsTabs/ThemeMockup.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Numerics; -using Dalamud.Bindings.ImGui; -using HellionChat.Themes; -using HellionChat.Util; - -namespace HellionChat.Ui.SettingsTabs; - -internal static class ThemeMockup -{ - // Mini chat window mockup drawn directly into the WindowDrawList. - // No textures, no per-frame allocations — pure AddRectFilled/AddText. - public static void Draw(Vector2 origin, Vector2 size, Theme theme) - { - var draw = ImGui.GetWindowDrawList(); - var c = theme.Colors; - - // Window background - draw.AddRectFilled( - origin, - origin + size, - ColourUtil.RgbaToAbgr(c.WindowBg | 0xFFu), - theme.Layout.WindowRounding - ); - - // Title bar - var titleHeight = 14f; - draw.AddRectFilled( - origin, - new Vector2(origin.X + size.X, origin.Y + titleHeight), - ColourUtil.RgbaToAbgr(c.Identity), - theme.Layout.WindowRounding - ); - - // Tab bar (3 tabs) - var tabY = origin.Y + titleHeight + 4f; - var tabHeight = 12f; - for (var i = 0; i < 3; i++) - { - var tabX = origin.X + 6f + i * 28f; - var color = i == 0 ? c.FrameBg : c.ChildBg; - draw.AddRectFilled( - new Vector2(tabX, tabY), - new Vector2(tabX + 26f, tabY + tabHeight), - ColourUtil.RgbaToAbgr(color), - theme.Layout.TabRounding - ); - - if (i == 0) // active pill - { - draw.AddRectFilled( - new Vector2(tabX, tabY + tabHeight - 2f), - new Vector2(tabX + 26f, tabY + tabHeight), - ColourUtil.RgbaToAbgr(c.Primary) - ); - } - } - - // Message card row - var rowY = tabY + tabHeight + 6f; - var rowHeight = 18f; - draw.AddRectFilled( - new Vector2(origin.X + 6f, rowY), - new Vector2(origin.X + size.X - 6f, rowY + rowHeight), - ColourUtil.RgbaToAbgr(c.Surface), - 2f - ); - - // Accent button (bottom right) - var btnW = 28f; - var btnH = 10f; - var btnX = origin.X + size.X - btnW - 6f; - var btnY = origin.Y + size.Y - btnH - 6f; - draw.AddRectFilled( - new Vector2(btnX, btnY), - new Vector2(btnX + btnW, btnY + btnH), - ColourUtil.RgbaToAbgr(c.Accent), - theme.Layout.FrameRounding - ); - - // Mockup border - draw.AddRect( - origin, - origin + size, - ColourUtil.RgbaToAbgr(c.Border), - theme.Layout.WindowRounding - ); - } -} diff --git a/HellionChat/Ui/SettingsTabs/Window.cs b/HellionChat/Ui/SettingsTabs/Window.cs deleted file mode 100644 index 38e2e28..0000000 --- a/HellionChat/Ui/SettingsTabs/Window.cs +++ /dev/null @@ -1,198 +0,0 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface.Utility.Raii; -using HellionChat.Resources; -using HellionChat.Util; - -namespace HellionChat.Ui.SettingsTabs; - -internal sealed class Window : ISettingsTab -{ - private Plugin Plugin { get; } - private Configuration Mutable { get; } - - public string Name => HellionStrings.Settings_Tab_Window + "###tabs-window"; - - internal Window(Plugin plugin, Configuration mutable) - { - Plugin = plugin; - Mutable = mutable; - } - - public void Draw(bool sectionJustEntered) - { - DrawHideSection(sectionJustEntered); - ImGui.Spacing(); - DrawInactivityHideSection(sectionJustEntered); - ImGui.Spacing(); - DrawFrameSection(sectionJustEntered); - } - - private void DrawHideSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Hide); - if (!tree.Success) - { - return; - } - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_HideChat_Name, ref Mutable.HideChat); - ImGuiUtil.HelpMarker(Language.Options_HideChat_Description); - - ImGui.Checkbox( - Language.Options_HideDuringCutscenes_Name, - ref Mutable.HideDuringCutscenes - ); - ImGuiUtil.HelpMarker( - string.Format(Language.Options_HideDuringCutscenes_Description, Plugin.PluginName) - ); - - ImGui.Checkbox( - Language.Options_HideWhenNotLoggedIn_Name, - ref Mutable.HideWhenNotLoggedIn - ); - ImGuiUtil.HelpMarker( - string.Format(Language.Options_HideWhenNotLoggedIn_Description, Plugin.PluginName) - ); - - ImGui.Checkbox(Language.Options_HideWhenUiHidden_Name, ref Mutable.HideWhenUiHidden); - ImGuiUtil.HelpMarker( - string.Format(Language.Options_HideWhenUiHidden_Description, Plugin.PluginName) - ); - - ImGui.Checkbox( - Language.Options_HideInLoadingScreens_Name, - ref Mutable.HideInLoadingScreens - ); - ImGuiUtil.HelpMarker( - string.Format(Language.Options_HideInLoadingScreens_Description, Plugin.PluginName) - ); - - ImGui.Checkbox(Language.Options_HideInBattle_Name, ref Mutable.HideInBattle); - ImGuiUtil.HelpMarker(Language.Options_HideInBattle_Description); - - ImGui.Checkbox( - Language.Options_HideInNewGamePlusMenu_Name, - ref Mutable.HideInNewGamePlusMenu - ); - ImGuiUtil.HelpMarker(Language.Options_HideInNewGamePlusMenu_Description); - } - } - - private void DrawInactivityHideSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_InactivityHide); - if (!tree.Success) - { - return; - } - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_HideWhenInactive_Name, ref Mutable.HideWhenInactive); - ImGuiUtil.HelpMarker(Language.Options_HideWhenInactive_Description); - - if (!Mutable.HideWhenInactive) - { - return; - } - - ImGuiUtil.InputIntVertical( - Language.Options_InactivityHideTimeout_Name, - Language.Options_InactivityHideTimeout_Description, - ref Mutable.InactivityHideTimeout, - 1, - 10 - ); - // Floor at 2 seconds to prevent self-soft-lock. - Mutable.InactivityHideTimeout = Math.Max(2, Mutable.InactivityHideTimeout); - - using (ImRaii.Disabled(Mutable.HideInBattle)) - { - ImGui.Checkbox( - Language.Options_InactivityHideActiveDuringBattle_Name, - ref Mutable.InactivityHideActiveDuringBattle - ); - ImGuiUtil.HelpMarker(Language.Options_InactivityHideActiveDuringBattle_Description); - } - - using var channelTree = ImRaii.TreeNode(Language.Options_InactivityHideChannels_Name); - if (!channelTree.Success) - { - return; - } - - if ( - ImGuiUtil.CtrlShiftButton( - Language.Options_InactivityHideChannels_All_Label, - Language.Options_InactivityHideChannels_Button_Tooltip - ) - ) - { - Mutable.InactivityHideChannelsV2 = TabsUtil.AllChannels(); - Mutable.InactivityHideExtraChatAll = true; - Mutable.InactivityHideExtraChatChannels = []; - } - - ImGui.SameLine(); - if ( - ImGuiUtil.CtrlShiftButton( - Language.Options_InactivityHideChannels_None_Label, - Language.Options_InactivityHideChannels_Button_Tooltip - ) - ) - { - Mutable.InactivityHideChannelsV2 = []; - Mutable.InactivityHideExtraChatAll = false; - Mutable.InactivityHideExtraChatChannels = []; - } - - ImGui.Spacing(); - - ImGuiUtil.ChannelSelector( - Language.Options_Tabs_Channels, - Mutable.InactivityHideChannelsV2 - ); - ImGuiUtil.ExtraChatSelector( - Language.Options_Tabs_ExtraChatChannels, - ref Mutable.InactivityHideExtraChatAll, - Mutable.InactivityHideExtraChatChannels - ); - } - } - - private void DrawFrameSection(bool sectionJustEntered) - { - if (sectionJustEntered) - ImGui.SetNextItemOpen(false); - using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Frame); - if (!tree.Success) - { - return; - } - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGui.Checkbox(Language.Options_CanMove_Name, ref Mutable.CanMove); - ImGui.Checkbox(Language.Options_CanResize_Name, ref Mutable.CanResize); - - ImGui.Checkbox( - HellionStrings.Settings_Window_PopOutInputEnabled_Name, - ref Mutable.PopOutInputEnabled - ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_Window_PopOutInputEnabled_Description); - - ImGui.Spacing(); - - // Fallback for off-screen windows after a display layout change. - if (ImGui.Button(HellionStrings.Settings_Window_ResetPosition_Name)) - Plugin.ChatLogWindow.RequestPositionReset = true; - ImGuiUtil.HelpMarker(HellionStrings.Settings_Window_ResetPosition_Description); - } - } -}