Files
HellionChat/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs
T
JonKazama-Hellion 9ea9e96145 fix: close what the style review found, starting with a gate the metadata skipped
The real defect first. RefreshDatabaseMetadata was the one worker of six
that never took the shared lock, and its flag was the one of six missing
from the tab's busy state. It calls MessageCount, which holds the read
lock, so a wipe could start while it was in there -- and the tab would
not have known to grey the button, because it could not see the worker.
Both halves fixed. The pattern is why: seven near-copies of one worker
skeleton, and each copy decided something slightly different.

The clear button failed silently when its thread could not start. The
most destructive control in the plugin, pressed, and nothing happens,
with no way to tell that from a wipe that worked -- while the three
harmless workers beside it do report. Maintenance was the mirror: its
comment promises refusals are said out loud, and then swallowed the
actual failure. Three start-failure paths also bypassed the notify
helper that carries the teardown check, three weeks after it was added
for exactly that.

The database numbers now wait for a real read, like the clear hint
already did. Zero bytes and zero messages read as an empty database, not
as a number nobody has fetched.

SelectionAfterDelete is gone, with its three tests. The accordion has no
selection, so its return value went into a discard -- a function
answering a question the interface does not ask, with green tests
guarding nothing. The project's own self-test README calls that the
anti-pattern of record.

Six new keys replaced by the translated orphans that already said the
same thing. A commit earlier in this cycle is literally called "stop
duplicating a key" and these went past it. The duplicate button also had
the label "Add", which is the one string out of ninety-four that was
never written.

Tests: CleanupDeleteTypes had none, and with the failsafe on -- how a
fresh config ships -- it is the path every cleanup takes. Four now,
including the one that matters: an empty list deletes nothing rather
than everything.

And a self-test for the gate wiring, which is what would have caught the
metadata worker. The unit tests prove the gate works; nothing proved the
workers use it.
2026-08-19 07:09:54 +02:00

262 lines
10 KiB
C#

using Dalamud.Bindings.ImGui;
using HellionChat.Resources;
using HellionChat.Ui.StyleEngine;
namespace HellionChat.Ui.Components.Settings.Tabs;
// First tab converted to the styled widgets. Everything here used to be stock
// ImGui: framed collapsing bars, checkboxes with their label on the right, and
// sliders glued to the left edge with their name trailing behind.
internal sealed class WindowTab
{
private readonly SettingsWidgets _w;
// Held rather than built per frame, and ordered to match the enum values
// passed alongside them.
private static readonly MainWindowLayoutMode[] LayoutValues =
[
MainWindowLayoutMode.Sidebar,
MainWindowLayoutMode.TopTabs,
];
// Built per call rather than cached: a runtime language switch has to reach
// these, and a static array would keep whichever language the plugin
// happened to start in.
private static string[] LayoutLabels =>
[
HellionStrings.Settings_Window_LayoutSidebar,
HellionStrings.Settings_Window_LayoutTopTabs,
];
public WindowTab(Plugin plugin, TokenResolver resolver)
{
_w = new SettingsWidgets(plugin, new SettingsPalette(resolver));
}
public void Draw()
{
// ASCII literals, not the visible titles: the keys have to survive a
// language change, and u8 literals cost no allocation.
if (_w.Section(ImGui.GetID("window.layout"u8), HellionStrings.Settings_Section_LayoutMode))
{
_w.SegmentRow(
ImGui.GetID("window.layout.mode"u8),
HellionStrings.Settings_Window_TabPlacement_Name,
HellionStrings.Settings_Window_TabPlacement_Description,
LayoutValues,
LayoutLabels,
() => Plugin.Config.MainWindowLayoutMode,
v => Plugin.Config.MainWindowLayoutMode = v
);
}
if (
_w.Section(
ImGui.GetID("window.style"u8),
HellionStrings.Settings_ThemeAndLayout_WindowStyle_Heading
)
)
{
_w.ToggleRow(
ImGui.GetID("window.style.titlebar"u8),
HellionStrings.Settings_Window_TitleBar_Name,
null,
() => Plugin.Config.ShowTitleBar,
v => Plugin.Config.ShowTitleBar = v
);
_w.ToggleRow(
ImGui.GetID("window.style.popouttitlebar"u8),
HellionStrings.Settings_Window_PopoutTitleBar_Name,
null,
() => Plugin.Config.ShowPopOutTitleBar,
v => Plugin.Config.ShowPopOutTitleBar = v
);
_w.ToggleRow(
ImGui.GetID("window.style.hidebutton"u8),
Language.Options_ShowHideButton_Name,
null,
() => Plugin.Config.ShowHideButton,
v => Plugin.Config.ShowHideButton = v
);
}
if (_w.Section(ImGui.GetID("window.opacity"u8), HellionStrings.Settings_Section_Opacity))
{
_w.SliderFloatRow(
ImGui.GetID("window.opacity.active"u8),
HellionStrings.Theme_WindowOpacity_Label,
null,
() => Plugin.Config.WindowOpacity,
v => Plugin.Config.WindowOpacity = v,
0.1f,
1f
);
_w.SliderFloatRow(
ImGui.GetID("window.opacity.inactive"u8),
HellionStrings.Settings_ThemeAndLayout_WindowOpacityInactive_Name,
HellionStrings.Settings_ThemeAndLayout_WindowOpacityInactive_Description,
() => Plugin.Config.WindowOpacityInactive,
v => Plugin.Config.WindowOpacityInactive = v,
0.1f,
1f
);
}
if (
_w.Section(
ImGui.GetID("window.resize"u8),
HellionStrings.Settings_Section_ResizeBehaviour
)
)
{
_w.ToggleRow(
ImGui.GetID("window.resize.move"u8),
HellionStrings.Settings_Window_AllowMove_Name,
null,
() => Plugin.Config.CanMove,
v => Plugin.Config.CanMove = v
);
_w.ToggleRow(
ImGui.GetID("window.resize.resize"u8),
HellionStrings.Settings_Window_AllowResize_Name,
null,
() => Plugin.Config.CanResize,
v => Plugin.Config.CanResize = v
);
_w.SliderIntRow(
ImGui.GetID("window.resize.threshold"u8),
HellionStrings.Settings_Window_SidebarThreshold_Name,
HellionStrings.Settings_Window_SidebarThreshold_Description,
() => Plugin.Config.SidebarAutoSwitchThresholdPx,
v => Plugin.Config.SidebarAutoSwitchThresholdPx = v,
200,
800
);
}
if (
_w.Section(
ImGui.GetID("window.preview"u8),
HellionStrings.Settings_Section_InputPreview,
open: false
)
)
{
_w.EnumComboRow(
ImGui.GetID("window.preview.position"u8),
HellionStrings.Settings_Window_PreviewPosition_Name,
null,
() => Plugin.Config.PreviewPosition,
v => Plugin.Config.PreviewPosition = v,
v => v.Name()
);
_w.ToggleRow(
ImGui.GetID("window.preview.onlyif"u8),
HellionStrings.Settings_Window_PreviewOnlyTyping_Name,
null,
() => Plugin.Config.OnlyPreviewIf,
v => Plugin.Config.OnlyPreviewIf = v
);
// Partner of the toggle above and useless without it: how many
// characters have to be typed before the preview appears.
_w.SliderIntRow(
ImGui.GetID("window.preview.minimum"u8),
Language.Options_PreviewMinimum_Name,
Language.Options_PreviewMinimum_Description,
() => Plugin.Config.PreviewMinimum,
v => Plugin.Config.PreviewMinimum = v,
0,
20
);
}
// HideChat is the one with real bite: it defaults to on, it suppresses
// the game's own chat window, and the only other way to it is a
// right-click item that can turn it on and never off. Somebody who used
// that item once had to edit JSON to get their chat back.
if (_w.Section(ImGui.GetID("window.hide"u8), HellionStrings.Settings_Section_Hide))
{
_w.ToggleRow(
ImGui.GetID("window.hide.chat"u8),
Language.Options_HideChat_Name,
Language.Options_HideChat_Description,
() => Plugin.Config.HideChat,
v => Plugin.Config.HideChat = v
);
_w.ToggleRow(
ImGui.GetID("window.hide.uihidden"u8),
Language.Options_HideWhenUiHidden_Name,
Language.Options_HideWhenUiHidden_Description,
() => Plugin.Config.HideWhenUiHidden,
v => Plugin.Config.HideWhenUiHidden = v
);
_w.ToggleRow(
ImGui.GetID("window.hide.loading"u8),
Language.Options_HideInLoadingScreens_Name,
Language.Options_HideInLoadingScreens_Description,
() => Plugin.Config.HideInLoadingScreens,
v => Plugin.Config.HideInLoadingScreens = v
);
_w.ToggleRow(
ImGui.GetID("window.hide.newgameplus"u8),
Language.Options_HideInNewGamePlusMenu_Name,
Language.Options_HideInNewGamePlusMenu_Description,
() => Plugin.Config.HideInNewGamePlusMenu,
v => Plugin.Config.HideInNewGamePlusMenu = v
);
// The three that lost their reader with the chat window. Pressing
// the chat key during a cutscene brings the chat back for that
// cutscene; combat has no such escape, exactly as in v1.5.6.
_w.ToggleRow(
ImGui.GetID("window.hide.cutscenes"u8),
Language.Options_HideDuringCutscenes_Name,
Language.Options_HideDuringCutscenes_Description,
() => Plugin.Config.HideDuringCutscenes,
v => Plugin.Config.HideDuringCutscenes = v
);
_w.ToggleRow(
ImGui.GetID("window.hide.battle"u8),
Language.Options_HideInBattle_Name,
Language.Options_HideInBattle_Description,
() => Plugin.Config.HideInBattle,
v => Plugin.Config.HideInBattle = v
);
_w.ToggleRow(
ImGui.GetID("window.hide.notloggedin"u8),
Language.Options_HideWhenNotLoggedIn_Name,
Language.Options_HideWhenNotLoggedIn_Description,
() => Plugin.Config.HideWhenNotLoggedIn,
v => Plugin.Config.HideWhenNotLoggedIn = v
);
}
if (
_w.Section(
ImGui.GetID("window.tooltips"u8),
HellionStrings.Settings_Section_LinksTooltips,
open: false
)
)
{
_w.ToggleRow(
ImGui.GetID("window.tooltips.native"u8),
Language.Options_NativeItemTooltips_Name,
Language.Options_NativeItemTooltips_Description,
() => Plugin.Config.NativeItemTooltips,
v => Plugin.Config.NativeItemTooltips = v
);
_w.SliderIntRow(
ImGui.GetID("window.tooltips.offset"u8),
Language.Options_TooltipOffset_Name,
Language.Options_TooltipOffset_Desc,
() => (int)Plugin.Config.TooltipOffset,
v => Plugin.Config.TooltipOffset = v,
0,
200
);
}
}
}