diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index e729aaf..0de13eb 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -208,20 +208,24 @@ internal static class PluginHostFactory )); services.AddSingleton(sp => new Ui.Components.Settings.Tabs.GeneralTab( sp.GetRequiredService(), - sp.GetRequiredService() + sp.GetRequiredService(), + sp.GetRequiredService() )); services.AddSingleton(sp => new Ui.Components.Settings.Tabs.ChatTab( - sp.GetRequiredService() + sp.GetRequiredService(), + sp.GetRequiredService() )); services.AddSingleton(sp => new Ui.Components.Settings.Tabs.WindowTab( sp.GetRequiredService(), sp.GetRequiredService() )); services.AddSingleton(sp => new Ui.Components.Settings.Tabs.ChannelsTab( - sp.GetRequiredService() + sp.GetRequiredService(), + sp.GetRequiredService() )); services.AddSingleton(sp => new Ui.Components.Settings.Tabs.DataPrivacyTab( - sp.GetRequiredService() + sp.GetRequiredService(), + sp.GetRequiredService() )); services.AddSingleton(sp => new Ui.Components.Settings.Tabs.AboutTab( sp.GetRequiredService(), diff --git a/HellionChat/Ui/Components/Settings/SettingsWidgets.cs b/HellionChat/Ui/Components/Settings/SettingsWidgets.cs index e2dda3b..826ec2d 100644 --- a/HellionChat/Ui/Components/Settings/SettingsWidgets.cs +++ b/HellionChat/Ui/Components/Settings/SettingsWidgets.cs @@ -71,6 +71,20 @@ internal sealed class SettingsWidgets // The whole row toggles, label included. The switch itself gets its own // invisible button because SettingRow's hit area stops at the label column, // and clicking the control is what a user tries first. + // Escape hatch for controls the helpers do not cover -- a keybind capture, + // a picker with side effects. The caller draws whatever it likes into the + // control column and keeps its own save logic. + internal void Row( + uint id, + string label, + string? description, + Action drawControl + ) + { + EnsureFrame(); + SettingRow.Draw(id, label, description, _row, drawControl); + } + internal void ToggleRow( uint id, string label, diff --git a/HellionChat/Ui/Components/Settings/Tabs/ChannelsTab.cs b/HellionChat/Ui/Components/Settings/Tabs/ChannelsTab.cs index aeb6edb..09119f8 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/ChannelsTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/ChannelsTab.cs @@ -1,111 +1,116 @@ using Dalamud.Bindings.ImGui; using HellionChat.Resources; -using HellionChat.Util; +using HellionChat.Ui.StyleEngine; namespace HellionChat.Ui.Components.Settings.Tabs; internal sealed class ChannelsTab { - private readonly Plugin _plugin; private readonly SettingsWidgets _w; - public ChannelsTab(Plugin plugin) + public ChannelsTab(Plugin plugin, TokenResolver resolver) { - _plugin = plugin; - _w = new SettingsWidgets(plugin); + _w = new SettingsWidgets(plugin, new SettingsPalette(resolver)); } public void Draw() { if ( - ImGui.CollapsingHeader( - HellionStrings.Settings_Section_AutoTellTabs, - ImGuiTreeNodeFlags.DefaultOpen + _w.Section( + ImGui.GetID("channels.autotell"u8), + HellionStrings.Settings_Section_AutoTellTabs ) ) { - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("channels.autotell.enable"u8), HellionStrings.ChatLog_AutoTellTabs_Enable_Name, + HellionStrings.ChatLog_AutoTellTabs_Enable_Description, () => Plugin.Config.EnableAutoTellTabs, v => Plugin.Config.EnableAutoTellTabs = v ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Enable_Description); - - _w.SliderInt( + _w.SliderIntRow( + ImGui.GetID("channels.autotell.limit"u8), HellionStrings.ChatLog_AutoTellTabs_Limit_Name, + HellionStrings.ChatLog_AutoTellTabs_Limit_Description, () => Plugin.Config.AutoTellTabsLimit, v => Plugin.Config.AutoTellTabsLimit = v, 1, 50 ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Limit_Description); - - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("channels.autotell.compact"u8), HellionStrings.ChatLog_AutoTellTabs_Compact_Name, + HellionStrings.ChatLog_AutoTellTabs_Compact_Description, () => Plugin.Config.AutoTellTabsCompactDisplay, v => Plugin.Config.AutoTellTabsCompactDisplay = v ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Compact_Description); - - _w.SliderInt( + _w.SliderIntRow( + ImGui.GetID("channels.autotell.preload"u8), HellionStrings.Privacy_AutoTellTabs_Preload_Name, + HellionStrings.Privacy_AutoTellTabs_Preload_Description, () => Plugin.Config.AutoTellTabsHistoryPreload, v => Plugin.Config.AutoTellTabsHistoryPreload = v, 0, 200 ); - ImGuiUtil.HelpMarker(HellionStrings.Privacy_AutoTellTabs_Preload_Description); - - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("channels.autotell.greeted"u8), HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Name, + HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Description, () => Plugin.Config.AutoTellTabsShowGreetedToggle, v => Plugin.Config.AutoTellTabsShowGreetedToggle = v ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Description); - - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("channels.autotell.popout"u8), HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Name, + HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Description, () => Plugin.Config.AutoTellTabsOpenAsPopout, v => Plugin.Config.AutoTellTabsOpenAsPopout = v ); - ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Description); - // Written for this screen and never shown until now. It names the - // one setting in a third-party plugin that silently stops auto-tell - // tabs from ever opening, which is not something a user guesses. + // Written for this screen and never shown until this cycle. It names + // the one setting in a third-party plugin that silently stops + // auto-tell tabs from ever opening, which is not something a user + // works out alone. ImGui.Spacing(); ImGui.TextWrapped(HellionStrings.ChatLog_AutoTellTabs_ConflictHint); } - if (ImGui.CollapsingHeader("Tell auto-open mode", ImGuiTreeNodeFlags.DefaultOpen)) + if (_w.Section(ImGui.GetID("channels.autoopen"u8), "Tell auto-open")) { - _w.EnumCombo( + _w.EnumComboRow( + ImGui.GetID("channels.autoopen.mode"u8), "Tell auto-open mode", + "Where a tell opens when it arrives.", () => Plugin.Config.TellAutoOpenMode, v => Plugin.Config.TellAutoOpenMode = v, v => v.Name() ); - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("channels.autoopen.switch"u8), "Switch to the tab on every tell", + "Otherwise the tab opens in the background after the first one.", () => Plugin.Config.TellAutoOpenSwitchAlways, v => Plugin.Config.TellAutoOpenSwitchAlways = v ); } - if (ImGui.CollapsingHeader("Sidebar")) + if (_w.Section(ImGui.GetID("channels.sidebar"u8), "Sidebar", open: false)) { // Bounds come from the constants rather than repeating the numbers, // so the slider cannot drift away from the clamp in Sidebar.GetWidth. // The stored value is unscaled; display scaling is applied where the // sidebar is drawn. - _w.SliderInt( + _w.SliderIntRow( + ImGui.GetID("channels.sidebar.width"u8), HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Name, + HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Description, () => Plugin.Config.SidebarWidth, v => Plugin.Config.SidebarWidth = v, (int)Sidebar.MinSidebarWidth, (int)Sidebar.MaxSidebarWidth ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Description); } } } diff --git a/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs b/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs index b7a3a9f..31ec8aa 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs @@ -1,68 +1,79 @@ using Dalamud.Bindings.ImGui; using HellionChat.Resources; -using HellionChat.Util; +using HellionChat.Ui.StyleEngine; namespace HellionChat.Ui.Components.Settings.Tabs; internal sealed class ChatTab { - private readonly Plugin _plugin; private readonly SettingsWidgets _w; - public ChatTab(Plugin plugin) + public ChatTab(Plugin plugin, TokenResolver resolver) { - _plugin = plugin; - _w = new SettingsWidgets(plugin); + _w = new SettingsWidgets(plugin, new SettingsPalette(resolver)); } public void Draw() { - if (ImGui.CollapsingHeader("Display modes", ImGuiTreeNodeFlags.DefaultOpen)) + if (_w.Section(ImGui.GetID("chat.display"u8), "Display modes")) { - _w.Toggle( - "Compact density (card vs compact)", + _w.ToggleRow( + ImGui.GetID("chat.display.density"u8), + "Compact density", + "Trades the card layout for one line per message.", () => Plugin.Config.UseCompactDensity, v => Plugin.Config.UseCompactDensity = v ); - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("chat.display.clock"u8), "24-hour clock", + null, () => Plugin.Config.Use24HourClock, v => Plugin.Config.Use24HourClock = v ); - _w.EnumCombo( + + // Descriptions move out of the help markers and onto the row. They + // were written to be read; a (?) the user has to hover is where an + // explanation goes to be ignored. + _w.EnumComboRow( + ImGui.GetID("chat.display.worldsuffix"u8), HellionStrings.Settings_Chat_WorldSuffix_Name, + HellionStrings.Settings_Chat_WorldSuffix_Description, () => Plugin.Config.WorldSuffixMode, v => Plugin.Config.WorldSuffixMode = v, v => v.Name() ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_WorldSuffix_Description); - _w.EnumCombo( + _w.EnumComboRow( + ImGui.GetID("chat.display.nameform"u8), HellionStrings.Settings_Chat_NameForm_Name, + HellionStrings.Settings_Chat_NameForm_Description, () => Plugin.Config.NameFormMode, v => Plugin.Config.NameFormMode = v, v => v.Name() ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_NameForm_Description); } - if (ImGui.CollapsingHeader("Command help")) + if (_w.Section(ImGui.GetID("chat.commandhelp"u8), "Command help", open: false)) { - _w.EnumCombo( + _w.EnumComboRow( + ImGui.GetID("chat.commandhelp.side"u8), "Command help side", + "Which side the command hint list appears on while typing.", () => Plugin.Config.CommandHelpSide, v => Plugin.Config.CommandHelpSide = v, v => v.Name() ); } - if (ImGui.CollapsingHeader("Plugin disclosure")) + if (_w.Section(ImGui.GetID("chat.disclosure"u8), "Plugin disclosure", open: false)) { - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("chat.disclosure.notify"u8), HellionStrings.Settings_Chat_NotifyPluginDisclosure_Name, + HellionStrings.Settings_Chat_NotifyPluginDisclosure_Description, () => Plugin.Config.NotifyPluginDisclosure, v => Plugin.Config.NotifyPluginDisclosure = v ); - ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_NotifyPluginDisclosure_Description); } } } diff --git a/HellionChat/Ui/Components/Settings/Tabs/DataPrivacyTab.cs b/HellionChat/Ui/Components/Settings/Tabs/DataPrivacyTab.cs index d7803bb..53e2d94 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/DataPrivacyTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/DataPrivacyTab.cs @@ -10,35 +10,37 @@ internal sealed class DataPrivacyTab private readonly Plugin _plugin; private readonly SettingsWidgets _w; - public DataPrivacyTab(Plugin plugin) + public DataPrivacyTab(Plugin plugin, Ui.StyleEngine.TokenResolver resolver) { _plugin = plugin; - _w = new SettingsWidgets(plugin); + _w = new SettingsWidgets(plugin, new SettingsPalette(resolver)); } public void Draw() { - if (ImGui.CollapsingHeader("Logging", ImGuiTreeNodeFlags.DefaultOpen)) + if (_w.Section(ImGui.GetID("privacy.logging"u8), HellionStrings.Retention_Heading)) { - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("privacy.logging.enabled"u8), HellionStrings.Retention_Enabled_Name, + HellionStrings.Retention_Enabled_Description, () => Plugin.Config.RetentionEnabled, v => Plugin.Config.RetentionEnabled = v ); - ImGuiUtil.HelpMarker(HellionStrings.Retention_Enabled_Description); // Down to 0, which DeleteByRetentionPolicy reads as "keep forever" // for channels without an override. The slider started at 1, so the // one value that means "never delete anything" was unreachable // through the UI while the label promised it. - _w.SliderInt( + _w.SliderIntRow( + ImGui.GetID("privacy.logging.default"u8), HellionStrings.Retention_Default_Label, + HellionStrings.Retention_Default_Help, () => Plugin.Config.RetentionDefaultDays, v => Plugin.Config.RetentionDefaultDays = v, 0, 365 ); - ImGuiUtil.HelpMarker(HellionStrings.Retention_Default_Help); // RetentionLastRunAt defaults to MinValue on a fresh install, which // would render as "0001-01-01 00:00" and look like a bug; the "Never" @@ -54,22 +56,31 @@ internal sealed class DataPrivacyTab DrawRetentionOverrides(); } - if (ImGui.CollapsingHeader("Privacy filter", ImGuiTreeNodeFlags.DefaultOpen)) + if ( + _w.Section( + ImGui.GetID("privacy.filter"u8), + HellionStrings.Settings_Section_PrivacyFilter + ) + ) { - _w.Toggle( - "Enable privacy filter", + _w.ToggleRow( + ImGui.GetID("privacy.filter.enabled"u8), + HellionStrings.Privacy_FilterEnabled_Name, + HellionStrings.Privacy_FilterEnabled_Description, () => Plugin.Config.PrivacyFilterEnabled, v => Plugin.Config.PrivacyFilterEnabled = v ); - DrawPrivacyPersistChannelsGrid(); - _w.Toggle( - "Persist unknown channels", + _w.ToggleRow( + ImGui.GetID("privacy.filter.unknown"u8), + HellionStrings.Privacy_PersistUnknown_Name, + null, () => Plugin.Config.PrivacyPersistUnknownChannels, v => Plugin.Config.PrivacyPersistUnknownChannels = v ); + DrawPrivacyPersistChannelsGrid(); } - if (ImGui.CollapsingHeader("Telemetry")) + if (_w.Section(ImGui.GetID("privacy.telemetry"u8), "Telemetry", open: false)) { // Read-only placeholder; no telemetry is wired in v1.7.0. Do not // promote this to a toggle without an explicit Sub-Spec change. diff --git a/HellionChat/Ui/Components/Settings/Tabs/GeneralTab.cs b/HellionChat/Ui/Components/Settings/Tabs/GeneralTab.cs index 956dc61..cd76c42 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/GeneralTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/GeneralTab.cs @@ -1,5 +1,6 @@ using Dalamud.Bindings.ImGui; using HellionChat.Resources; +using HellionChat.Ui.StyleEngine; using HellionChat.Util; namespace HellionChat.Ui.Components.Settings.Tabs; @@ -16,10 +17,10 @@ internal sealed class GeneralTab // localised label never affects the ordering. private static readonly LanguageOverride[] LanguageOrder = BuildLanguageOrder(); - public GeneralTab(Plugin plugin, FontManager fonts) + public GeneralTab(Plugin plugin, FontManager fonts, TokenResolver resolver) { _plugin = plugin; - _w = new SettingsWidgets(plugin); + _w = new SettingsWidgets(plugin, new SettingsPalette(resolver)); _fonts = fonts; } @@ -33,17 +34,19 @@ internal sealed class GeneralTab public void Draw() { - if (ImGui.CollapsingHeader("Behavior", ImGuiTreeNodeFlags.DefaultOpen)) + if (_w.Section(ImGui.GetID("general.behaviour"u8), "Behaviour")) { - _w.Toggle( - "Reduce motion (no theme crossfade)", + _w.ToggleRow( + ImGui.GetID("general.behaviour.reducemotion"u8), + "Reduce motion", + "Turns off theme crossfades, hover fades and the drifting motes.", () => Plugin.Config.ReduceMotion, v => Plugin.Config.ReduceMotion = v ); DrawLanguagePicker(); } - if (ImGui.CollapsingHeader("Keybinds", ImGuiTreeNodeFlags.DefaultOpen)) + if (_w.Section(ImGui.GetID("general.keybinds"u8), "Keybinds")) { ImGui.TextDisabled("Click a button, then press the key combination. Esc clears."); DrawKeybind( @@ -61,20 +64,19 @@ internal sealed class GeneralTab DrawKeybindModePicker(); } - if (ImGui.CollapsingHeader("Notifications", ImGuiTreeNodeFlags.DefaultOpen)) + if (_w.Section(ImGui.GetID("general.notifications"u8), "Notifications")) { - _w.Toggle( + _w.ToggleRow( + ImGui.GetID("general.notifications.sounds"u8), Language.Options_PlaySounds_Name, + Language.Options_PlaySounds_Description, () => Plugin.Config.PlaySounds, v => Plugin.Config.PlaySounds = v ); - ImGuiUtil.HelpMarker(Language.Options_PlaySounds_Description); - } - - if (ImGui.CollapsingHeader("Volumes", ImGuiTreeNodeFlags.DefaultOpen)) - { - _w.SliderFloat( + _w.SliderFloatRow( + ImGui.GetID("general.notifications.volume"u8), "Custom sound volume", + null, () => Plugin.Config.CustomSoundVolume, v => Plugin.Config.CustomSoundVolume = v, 0f, @@ -95,18 +97,19 @@ internal sealed class GeneralTab for (var i = 0; i < LanguageOrder.Length; i++) labels[i] = LanguageOrder[i].Name(); - ImGui.SetNextItemWidth(200f); - if (ImGui.Combo(Language.Options_Language_Name, ref selected, labels, labels.Length)) - { - if (selected >= 0 && selected < LanguageOrder.Length) - ApplyLanguage(LanguageOrder[selected]); - } - - ImGuiUtil.HelpMarker( - string.Format(Language.Options_Language_Description, Plugin.PluginName) - + "\n\nSwitching rebuilds the font atlas, so the chat window goes blank" - + " for a moment. Scripts a language needs stay enabled afterwards;" - + " clear them under Appearance -> Fonts -> Extra glyphs." + _w.Row( + ImGui.GetID("general.behaviour.language"u8), + Language.Options_Language_Name, + "Switching rebuilds the font atlas, so the chat goes blank for a moment.", + ctx => + { + ImGui.SetNextItemWidth(ctx.ControlWidth); + if (ImGui.Combo("##hc-language", ref selected, labels, labels.Length)) + { + if (selected >= 0 && selected < LanguageOrder.Length) + ApplyLanguage(LanguageOrder[selected]); + } + } ); } @@ -155,18 +158,23 @@ internal sealed class GeneralTab for (var i = 0; i < values.Length; i++) labels[i] = values[i].Name(); - ImGui.SetNextItemWidth(200f); - if (ImGui.Combo(Language.Options_KeybindMode_Name, ref selected, labels, labels.Length)) - { - if (selected >= 0 && selected < values.Length) + _w.Row( + ImGui.GetID("general.keybinds.mode"u8), + Language.Options_KeybindMode_Name, + Plugin.Config.KeybindMode.Tooltip(), + ctx => { - Plugin.Config.KeybindMode = values[selected]; - _plugin.SaveConfig(); + ImGui.SetNextItemWidth(ctx.ControlWidth); + if (ImGui.Combo("##hc-keybindmode", ref selected, labels, labels.Length)) + { + if (selected >= 0 && selected < values.Length) + { + Plugin.Config.KeybindMode = values[selected]; + _plugin.SaveConfig(); + } + } } - } - - if (Plugin.Config.KeybindMode.Tooltip() is { } tip) - ImGuiUtil.HelpMarker(tip); + ); } // Wires the already-present ImGuiUtil.KeybindInput capture widget (dead/unwired