Files
HellionChat/HellionChat/Ui/Windows/SettingsWindow.cs
T
JonKazama-Hellion 04f8f1ace8 feat(settings): opaque settings window, contrast applied throughout
The settings window no longer inherits the chat window's transparency.
GlobalStyleScope pushes one opacity for every window in the plugin, so a value
chosen to keep the chat log out of the way was also deciding how readable a
settings dialog is.

The stronger reason is that contrast cannot be computed against a background
that is not there. Behind a translucent window the real background is the game:
a black cave one minute, a snowfield the next. Every foreground measured last
commit was measured against a colour it does not actually land on. Opaque makes
that measurement true.

BgAlpha only reaches the window fill, never the draw list, so the backdrop
cannot observe it -- it takes an explicit override instead of guessing from the
pushed WindowBg.

Contrast now runs through SettingsPalette, so every row and heading gets it
without each call site asking: 4.5:1 for labels and titles, 3:1 for descriptions
and accents. The lower floor on descriptions is deliberate. It preserves the
rank between the two lines of a row, which is why the description is dimmer at
all, while still guaranteeing it stays legible.

The toggle's border is in there too. It is the only thing marking an off switch,
and a border tone that blends into the surface leaves the control invisible.
2026-08-18 19:01:54 +02:00

142 lines
4.8 KiB
C#

using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using HellionChat.Resources;
using HellionChat.Ui.Components.Settings;
using HellionChat.Ui.Components.Settings.Tabs;
using Microsoft.Extensions.Logging;
namespace HellionChat.Ui.Windows;
// `internal` to match the Plugin.SettingsWindow property in W2; `public` here
// would raise CS0053 against the internal members. Matches MainWindow shape.
internal sealed class SettingsWindow : Window
{
private readonly Plugin _plugin;
private readonly TabSidebar _sidebar;
private readonly ContentArea _content;
private readonly ThemePicker _themePicker;
private readonly ColorPicker _colorPicker;
private readonly LivePreviewPanel _livePreview;
private readonly AppearanceTab _appearance;
private readonly GeneralTab _general;
private readonly ChatTab _chat;
private readonly WindowTab _window;
private readonly ChannelsTab _channels;
private readonly DataPrivacyTab _dataPrivacy;
private readonly AboutTab _about;
public SettingsWindow(
Plugin plugin,
TabSidebar sidebar,
ContentArea content,
ThemePicker themePicker,
ColorPicker colorPicker,
LivePreviewPanel livePreview,
AppearanceTab appearance,
GeneralTab general,
ChatTab chat,
WindowTab window,
ChannelsTab channels,
DataPrivacyTab dataPrivacy,
AboutTab about,
ILoggerFactory loggerFactory
)
: base($"{Language.Settings_Title.Format(Plugin.PluginName)}###chat2-settings")
{
_plugin = plugin;
_sidebar = sidebar;
_content = content;
_themePicker = themePicker;
_colorPicker = colorPicker;
_livePreview = livePreview;
_appearance = appearance;
_general = general;
_chat = chat;
_window = window;
_channels = channels;
_dataPrivacy = dataPrivacy;
_about = about;
_ = loggerFactory;
Size = new Vector2(720, 540);
SizeCondition = ImGuiCond.FirstUseEver;
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(600, 400),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
};
Flags = ImGuiWindowFlags.NoCollapse;
// Carry-over from v1.6.0 stub: Escape must not auto-close, and toggle
// events must not play default Dalamud window sounds.
RespectCloseHotkey = false;
DisableWindowSounds = true;
}
// The title is baked in at construction, so it kept the language the plugin
// started in even after the user switched. Everything else re-reads its
// strings per draw; this was the one frozen string.
public override void PreDraw()
{
var wanted = $"{Language.Settings_Title.Format(Plugin.PluginName)}###chat2-settings";
if (!string.Equals(WindowName, wanted, StringComparison.Ordinal))
WindowName = wanted;
// Opaque, unlike the chat window. GlobalStyleScope pushes the chat's
// opacity for every window in the plugin, and this one inherited it.
//
// Two reasons it should not. Contrast can only be computed against a
// known background, and behind a translucent window the real background
// is the game -- a black cave one minute, a snowfield the next, so the
// colour every foreground was just measured against is not the colour it
// lands on. And nobody adjusting a plugin needs to watch what is
// happening behind the dialog; the chat window is the one that has to
// stay out of the way.
BgAlpha = 1f;
}
public override void Draw()
{
_sidebar.Draw();
ImGui.SameLine();
_content.Draw(_sidebar.ActiveTab, RenderActiveTab);
}
private void RenderActiveTab(string tabId)
{
switch (tabId)
{
case "general":
_general.Draw();
break;
case "chat":
_chat.Draw();
break;
case "window":
_window.Draw();
break;
case "channels":
_channels.Draw();
break;
case "data-privacy":
_dataPrivacy.Draw();
break;
case "about":
_about.Draw();
break;
case "appearance":
_appearance.Draw();
break;
default:
ImGui.TextUnformatted($"[{tabId}] tab content lands in later task");
break;
}
}
// AboutTab is owned here (not MainWindow) and rendered only via the private
// RenderActiveTab; this exposes it for the integrations-status SelfTest.
internal AboutTab GetAboutTabForSelfTest() => _about;
}