Files
HellionChat/HellionChat/Ui/Components/Settings/ChatColourPicker.cs
T
JonKazama-Hellion 588b886537 fix(settings): remove the switches that do nothing
Six settings had a control, a saved value, translated labels, and no reader
anywhere in the plugin. Flipping them changed a byte on disk and nothing else.

Three sat next to each other in the Chat tab under timestamps, so the section
read as four related options where only one -- the 24-hour clock -- works.
FormatTimestamp consults nothing else.

The first-run wizard offered one of them too, on its visual step, and listed it
back in the summary as if it had been applied. That is the first screen a new
user sees, so it goes as well.

Their own strings show how long this has been drifting: the wizard called
PrettierTimestamps "relative time", the settings tab called the same field
"modern layout". Two different features, one boolean, neither implemented. That
is also why these are removed rather than wired up -- there is no single
behaviour to restore, and inventing one belongs in a cycle that plans it.

Config fields stay, with a comment. A stored value should survive until the
rendering it describes exists, and dropping them would silently reset anyone who
set them.

Found by an audit that turned up considerably more of this: 433 of 824
translated resource keys reach no code at all, and the export, cleanup, tab
editor and pin paths are complete but unreachable. That is its own cycle; this
commit only clears what actively lies to the user in the window we are working
on.
2026-08-18 14:41:07 +02:00

238 lines
8.5 KiB
C#

using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Themes;
using HellionChat.Util;
namespace HellionChat.Ui.Components.Settings;
// Restores the 1.5.6 chat-channel colour editor (1d3b429:Appearance.cs:189-243,
// 409-534): presets, the per-ChatType ColorEdit3 over Config.ChatColours (which
// ChunkRenderer consumes), reset/import-game-colour buttons, and the apply-banner
// that adopts the active theme's chatChannels. v1.6.0 saves live + refreshes cache.
internal sealed class ChatColourPicker
{
private readonly Plugin _plugin;
private readonly ThemeRegistry _themes;
private string? _applyDismissedFor;
private string? _lastSeenSlug;
public ChatColourPicker(Plugin plugin, ThemeRegistry themes)
{
_plugin = plugin;
_themes = themes;
}
// Drawn directly under the theme picker (1.5.6 placement) so the adopt prompt
// sits next to the theme that triggered it, not at the bottom of the tab.
public void DrawThemeAdoptBanner() => DrawApplyBanner(_themes.Active);
public void Draw()
{
if (!ImGui.CollapsingHeader(HellionStrings.Settings_Section_Colours))
return;
DrawPresetButtons();
ImGui.TextDisabled(HellionStrings.Settings_Appearance_Colours_PresetsHint);
ImGui.Spacing();
ImGui.Separator();
ImGui.Spacing();
// Discrete clicks (reset/import) persist at once. The ColorEdit3 drag only
// recolours live (Refresh, no disk write) and defers SaveConfig to release
// via IsItemDeactivatedAfterEdit, so dragging the colour wheel doesn't fire
// a full-config disk write every frame.
var commit = false;
var liveOnly = false;
foreach (var (_, types) in ChatTypeExt.SortOrder)
{
foreach (var type in types)
{
if (
ImGuiUtil.IconButton(
FontAwesomeIcon.UndoAlt,
$"{type}",
Language.Options_ChatColours_Reset
)
)
{
lock (_plugin.ConfigMapsLock)
Plugin.Config.ChatColours.Remove(type);
commit = true;
}
ImGui.SameLine();
if (
ImGuiUtil.IconButton(
FontAwesomeIcon.LongArrowAltDown,
$"{type}",
Language.Options_ChatColours_Import
)
)
{
var gameColour = _plugin.Functions.Chat.GetChannelColor(type);
lock (_plugin.ConfigMapsLock)
Plugin.Config.ChatColours[type] = gameColour ?? type.DefaultColor() ?? 0;
commit = true;
}
ImGui.SameLine();
var vec = Plugin.Config.ChatColours.TryGetValue(type, out var colour)
? ColourUtil.RgbaToVector3(colour)
: ColourUtil.RgbaToVector3(type.DefaultColor() ?? 0);
if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs))
{
// First edit of a channel without a default inserts a NEW key --
// that is the case that invalidates a running enumeration.
lock (_plugin.ConfigMapsLock)
Plugin.Config.ChatColours[type] = ColourUtil.Vector3ToRgba(vec);
liveOnly = true;
}
if (ImGui.IsItemDeactivatedAfterEdit())
commit = true;
}
}
if (commit)
ApplyChatColourChange();
else if (liveOnly)
GlobalParametersCache.Refresh();
ImGui.Spacing();
}
private void DrawPresetButtons()
{
var first = true;
foreach (var (_, preset) in ChatColourPresets.All)
{
if (!first)
ImGui.SameLine();
first = false;
var brand = preset.IsBrandPreset;
if (brand)
{
var border = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(255, 128, 200));
var btn = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(74, 42, 106));
ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(border, 1f));
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(btn, 1f));
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1.5f);
}
if (ImGui.Button(GetPresetLabel(preset)))
ApplyPreset(preset);
if (brand)
{
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)
lock (_plugin.ConfigMapsLock)
Plugin.Config.ChatColours[channel] = colour;
ApplyChatColourChange();
}
private void ApplyChatColourChange()
{
_plugin.SaveConfig();
GlobalParametersCache.Refresh();
}
// Offers to adopt the active theme's chatChannels into Config.ChatColours when
// they differ; dismissable per theme slug (matches 1.5.6 banner behaviour).
private void DrawApplyBanner(Theme active)
{
// Clear the per-theme dismiss whenever the active theme changes, so leaving
// a theme and returning re-offers the prompt (1.5.6 reset this on every switch).
if (active.Slug != _lastSeenSlug)
{
_applyDismissedFor = null;
_lastSeenSlug = active.Slug;
}
if (active.ChatColors is not { Channels.Count: > 0 } themeChatColors)
return;
if (_applyDismissedFor == active.Slug)
return;
var alreadyMatching = themeChatColors.Channels.All(kvp =>
Plugin.Config.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;
const float 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);
draw.AddText(
origin + new Vector2(12f, 10f),
ColourUtil.RgbaToAbgr(active.Colors.TextPrimary),
HellionStrings.Settings_Themes_ApplyChatColors_Hint
);
using (
ImRaii.PushColor(
ImGuiCol.Button,
new Vector4(ColourUtil.RgbaToVector3(active.Colors.Primary), 1f)
)
)
using (
ImRaii.PushColor(
ImGuiCol.ButtonHovered,
new Vector4(ColourUtil.RgbaToVector3(active.Colors.PrimaryLight), 1f)
)
)
using (
ImRaii.PushColor(
ImGuiCol.ButtonActive,
new Vector4(ColourUtil.RgbaToVector3(active.Colors.PrimaryDark), 1f)
)
)
{
ImGui.SetCursorScreenPos(origin + new Vector2(12f, 32f));
if (ImGui.Button(HellionStrings.Settings_Themes_ApplyChatColors_Apply))
{
foreach (var kvp in themeChatColors.Channels)
lock (_plugin.ConfigMapsLock)
Plugin.Config.ChatColours[kvp.Key] = kvp.Value;
_applyDismissedFor = active.Slug;
ApplyChatColourChange();
}
}
ImGui.SameLine();
if (ImGui.Button(HellionStrings.Settings_Themes_ApplyChatColors_Keep))
_applyDismissedFor = active.Slug;
ImGui.SetCursorScreenPos(origin + new Vector2(0f, height + 8f));
ImGui.Spacing();
}
}