feat(themes): restore the chat-channel colour editor in the appearance tab

Brings back the 1.5.6 per-channel colour editor lost in the v1.6.0 rewrite:
presets (incl. brand styling), per-ChatType reset/import-from-game/colour-edit
over Config.ChatColours, the colour-selected-input-channel toggle, and the
theme adopt-banner. The colour-edit drag recolours live but defers SaveConfig
to release (IsItemDeactivatedAfterEdit) to avoid a per-frame full-config write.
This commit is contained in:
2026-06-15 19:12:09 +02:00
parent 8cbf9c554f
commit 0352e6c199
3 changed files with 255 additions and 2 deletions
+6 -1
View File
@@ -177,12 +177,17 @@ internal static class PluginHostFactory
sp.GetRequiredService<Plugin>(),
sp.GetRequiredService<FontManager>()
));
services.AddSingleton(sp => new Ui.Components.Settings.ChatColourPicker(
sp.GetRequiredService<Plugin>(),
sp.GetRequiredService<ThemeRegistry>()
));
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.AppearanceTab(
sp.GetRequiredService<Ui.Components.Settings.ThemePicker>(),
sp.GetRequiredService<Ui.Components.Settings.ColorPicker>(),
sp.GetRequiredService<Ui.Components.Settings.LivePreviewPanel>(),
sp.GetRequiredService<Ui.Components.Settings.ThemeImportExportRow>(),
sp.GetRequiredService<Ui.Components.Settings.FontsSection>()
sp.GetRequiredService<Ui.Components.Settings.FontsSection>(),
sp.GetRequiredService<Ui.Components.Settings.ChatColourPicker>()
));
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.GeneralTab(
sp.GetRequiredService<Plugin>()
@@ -0,0 +1,242 @@
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();
if (
ImGui.Checkbox(
Language.Options_ColorSelectedInputChannelButton_Name,
ref Plugin.Config.ColorSelectedInputChannelButton
)
)
{
_plugin.SaveConfig();
}
ImGuiUtil.HelpMarker(Language.Options_ColorSelectedInputChannelButton_Description);
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
)
)
{
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);
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))
{
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)
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)
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();
}
}
@@ -12,13 +12,15 @@ internal sealed class AppearanceTab
private readonly LivePreviewPanel _preview;
private readonly ThemeImportExportRow _importExport;
private readonly FontsSection _fonts;
private readonly ChatColourPicker _chatColours;
public AppearanceTab(
ThemePicker picker,
ColorPicker color,
LivePreviewPanel preview,
ThemeImportExportRow importExport,
FontsSection fonts
FontsSection fonts,
ChatColourPicker chatColours
)
{
_picker = picker;
@@ -26,6 +28,7 @@ internal sealed class AppearanceTab
_preview = preview;
_importExport = importExport;
_fonts = fonts;
_chatColours = chatColours;
}
public void Draw()
@@ -38,12 +41,15 @@ internal sealed class AppearanceTab
if (left.Success)
{
_picker.Draw();
_chatColours.DrawThemeAdoptBanner();
ImGui.Spacing();
_importExport.Draw();
ImGui.Separator();
_fonts.Draw();
ImGui.Separator();
_color.Draw();
ImGui.Separator();
_chatColours.Draw();
}
}
ImGui.SameLine();