diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs
index 1b21657..5cbf40e 100755
--- a/ChatTwo/Configuration.cs
+++ b/ChatTwo/Configuration.cs
@@ -66,6 +66,11 @@ public class Configuration : IPluginConfiguration
// ChatTwo users skip it because the v6→v7 migration sets the flag.
public bool FirstRunCompleted;
+ // Hellion Chat global ImGui theme — applied to every plugin window in
+ // Plugin.Draw. Default ON; users who prefer the upstream Dalamud look
+ // can flip this off in the Privacy tab.
+ public bool HellionThemeEnabled = true;
+
public int GetRetentionDays(ChatType type)
{
if (RetentionPerChannelDays.TryGetValue(type, out var userOverride))
@@ -244,6 +249,7 @@ public class Configuration : IPluginConfiguration
RetentionLastRunAt = other.RetentionLastRunAt;
FirstRunCompleted = other.FirstRunCompleted;
+ HellionThemeEnabled = other.HellionThemeEnabled;
}
}
diff --git a/ChatTwo/Plugin.cs b/ChatTwo/Plugin.cs
index 959478a..702f281 100755
--- a/ChatTwo/Plugin.cs
+++ b/ChatTwo/Plugin.cs
@@ -385,6 +385,12 @@ public sealed class Plugin : IDalamudPlugin
private void Draw()
{
+ // Hellion theme is pushed once per frame here so every plugin window
+ // (chat log, settings, viewers, wizard, file dialog) renders with
+ // the same palette. Skipping the push leaves the upstream Dalamud
+ // look untouched for users who flipped the toggle off.
+ using IDisposable? _style = Config.HellionThemeEnabled ? HellionStyle.PushGlobal() : null;
+
ChatLogWindow.BeginFrame();
if (Config.HideInLoadingScreens && Condition[ConditionFlag.BetweenAreas])
diff --git a/ChatTwo/Resources/HellionStrings.Designer.cs b/ChatTwo/Resources/HellionStrings.Designer.cs
index 7781b01..13fcd83 100644
--- a/ChatTwo/Resources/HellionStrings.Designer.cs
+++ b/ChatTwo/Resources/HellionStrings.Designer.cs
@@ -130,4 +130,8 @@ internal class HellionStrings
internal static string Export_Success => Get(nameof(Export_Success));
internal static string Export_Empty => Get(nameof(Export_Empty));
internal static string Export_Error => Get(nameof(Export_Error));
+
+ internal static string Theme_Heading => Get(nameof(Theme_Heading));
+ internal static string Theme_Enabled_Name => Get(nameof(Theme_Enabled_Name));
+ internal static string Theme_Enabled_Description => Get(nameof(Theme_Enabled_Description));
}
diff --git a/ChatTwo/Resources/HellionStrings.de.resx b/ChatTwo/Resources/HellionStrings.de.resx
index bc615ac..ffec8b9 100644
--- a/ChatTwo/Resources/HellionStrings.de.resx
+++ b/ChatTwo/Resources/HellionStrings.de.resx
@@ -264,4 +264,13 @@
Export fehlgeschlagen, siehe /xllog
+
+ Erscheinungsbild
+
+
+ Hellion-Theme für alle Plugin-Fenster verwenden
+
+
+ Industrielle HUD-Palette mit cyan-blauen Aktionsfarben, schiefer-violetten Tabs und Bernstein-Akzenten für aktive Zustände, global angewendet auf Chat-Fenster, Einstellungen, Viewer und Wizard. Deaktivieren, um das Standard-Dalamud-Erscheinungsbild zu nutzen.
+
diff --git a/ChatTwo/Resources/HellionStrings.resx b/ChatTwo/Resources/HellionStrings.resx
index db4d961..8fced43 100644
--- a/ChatTwo/Resources/HellionStrings.resx
+++ b/ChatTwo/Resources/HellionStrings.resx
@@ -264,4 +264,13 @@
Export failed, see /xllog
+
+ Appearance
+
+
+ Use the Hellion theme across all plugin windows
+
+
+ Industrial HUD palette with cyan-teal action accents, slate-violet tabs and amber active highlights, applied globally to chat log, settings, viewers and the wizard. Disable to fall back to the default Dalamud look.
+
diff --git a/ChatTwo/Ui/FirstRunWizard.cs b/ChatTwo/Ui/FirstRunWizard.cs
index 0f7f57b..99928e7 100644
--- a/ChatTwo/Ui/FirstRunWizard.cs
+++ b/ChatTwo/Ui/FirstRunWizard.cs
@@ -41,8 +41,6 @@ public sealed class FirstRunWizard : Window
public override void Draw()
{
- using var _style = HellionStyle.Push();
-
ImGui.TextWrapped(HellionStrings.Wizard_Intro);
ImGui.Spacing();
ImGui.Separator();
diff --git a/ChatTwo/Ui/HellionStyle.cs b/ChatTwo/Ui/HellionStyle.cs
index 697308c..b71025c 100644
--- a/ChatTwo/Ui/HellionStyle.cs
+++ b/ChatTwo/Ui/HellionStyle.cs
@@ -5,65 +5,196 @@ using Dalamud.Interface.Utility.Raii;
namespace ChatTwo.Ui;
///
-/// Local ImGui style override applied inside Hellion-owned settings surfaces
-/// (Privacy tab, first-run wizard). Industrial HUD palette: deep slate
-/// background with cyan-teal accents and sharper geometric framing. Scoped
-/// via using-blocks so upstream Chat 2 tabs render in their original style.
+/// ImGui style override for Hellion Chat. Industrial HUD palette with three
+/// distinct accents — cyan-teal as the primary action color, industrial
+/// amber for active state highlights, slate-violet for title bars and
+/// active tabs — on a deep-slate frame background with steel borders.
+///
+/// Two entry points:
+/// Push — local color stack, scoped via using-block. Use inside
+/// Hellion-only surfaces (Privacy tab, first-run wizard).
+/// PushGlobal — full color + style variable stack. Pushed once per frame
+/// in Plugin.Draw so every Hellion-rendered window inherits
+/// the look. Cheap to pop because ImGui keeps its own stack.
///
internal static class HellionStyle
{
- // Palette — kept central so a future theme switch only edits one file.
- // Encoded as 0xRRGGBBAA, matching the convention ChatTwo uses elsewhere
- // (see Settings.cs Ko-fi buttons). RgbaToAbgr handles the byte swap.
- private const uint AccentRgba = 0x00B8D4FF; // cyan-teal accent
- private const uint AccentHoverRgba = 0x26C6DAFF;
- private const uint AccentActiveRgba = 0x00838FFF;
- private const uint FrameBgRgba = 0x102027FF; // deep slate
- private const uint FrameBgHoverRgba = 0x1B2C36FF;
- private const uint FrameBgActiveRgba = 0x263A45FF;
- private const uint BorderRgba = 0x37474FFF; // steel border
+ // Encoded as 0xRRGGBBAA, matching ChatTwo convention (see Settings.cs
+ // Ko-fi buttons). RgbaToAbgr handles the byte swap to the format ImGui
+ // expects.
+
+ // Primary — cyan-teal for actionable controls (buttons, checks, sliders).
+ private const uint PrimaryRgba = 0x00B8D4FF;
+ private const uint PrimaryHoverRgba = 0x26C6DAFF;
+ private const uint PrimaryActiveRgba = 0x00838FFF;
+
+ // Secondary — industrial amber, used as a warm highlight for active
+ // states (tab borders, resize grips, scrollbar grabs).
+ private const uint SecondaryRgba = 0xFFB300FF;
+ private const uint SecondaryHoverRgba = 0xFFC940FF;
+ private const uint SecondaryActiveRgba = 0xC68400FF;
+
+ // Tertiary — slate violet, reserved for title bars and the active tab
+ // background so identity beats out the cyan accent without competing
+ // with it on action controls.
+ private const uint TertiaryRgba = 0x7B61FFFF;
+ private const uint TertiaryHoverRgba = 0x9580FFFF;
+ private const uint TertiaryActiveRgba = 0x5E45D9FF;
+
+ // Surfaces — deep slate window/frame backgrounds, steel borders.
+ private const uint WindowBgRgba = 0x0E1A20FF;
+ private const uint ChildBgRgba = 0x102027FF;
+ private const uint PopupBgRgba = 0x102027FF;
+ private const uint FrameBgRgba = 0x162831FF;
+ private const uint FrameBgHoverRgba = 0x1F3540FF;
+ private const uint FrameBgActiveRgba = 0x274250FF;
+ private const uint BorderRgba = 0x37474FFF;
+ private const uint BorderShadowRgba = 0x00000000;
+
+ // Headers / collapsing-headers / tree nodes / selectables.
private const uint HeaderRgba = 0x1B2C36FF;
private const uint HeaderHoverRgba = 0x263A45FF;
private const uint HeaderActiveRgba = 0x324A57FF;
- private const uint TitleBgActiveRgba = 0x00838FFF;
- private const uint CheckMarkRgba = 0x00B8D4FF;
- private const uint SliderGrabRgba = 0x00B8D4FF;
- private const uint SliderGrabActiveRgba = 0x26C6DAFF;
+
+ // Title bars — tertiary identity for the active state.
+ private const uint TitleBgRgba = 0x0E1A20FF;
+ private const uint TitleBgActiveRgba = 0x5E45D9FF;
+ private const uint TitleBgCollapsedRgba = 0x0A1318FF;
+
+ // Tabs — tertiary tint, secondary highlight while hovered/unfocused.
+ private const uint TabRgba = 0x162831FF;
+ private const uint TabHoveredRgba = 0x9580FFFF;
+ private const uint TabActiveRgba = 0x7B61FFFF;
+ private const uint TabUnfocusedRgba = 0x12222AFF;
+ private const uint TabUnfocusedActiveRgba = 0x5E45D9FF;
+
+ // Scrollbar — slate base, secondary amber on grab.
+ private const uint ScrollbarBgRgba = 0x0E1A20FF;
+ private const uint ScrollbarGrabRgba = 0x37474FFF;
+ private const uint ScrollbarGrabHoveredRgba = 0xFFC940FF;
+ private const uint ScrollbarGrabActiveRgba = 0xFFB300FF;
+
+ // Resize grip — secondary amber for the active corner pull.
+ private const uint ResizeGripRgba = 0x37474FFF;
+ private const uint ResizeGripHoveredRgba = 0xFFC940FF;
+ private const uint ResizeGripActiveRgba = 0xFFB300FF;
+
+ // Separator and check mark / slider follow the primary cyan.
///
- /// Push the Hellion color stack. Returns a disposable bundle that pops
- /// every color in reverse order on Dispose. Use inside a `using` block.
+ /// Local color stack for Hellion-only surfaces. Cheap. Use inside a
+ /// `using var _ = HellionStyle.Push();` block.
///
internal static IDisposable Push()
{
var stack = new StackHandle();
- // Order matters less than count: each PushColor needs a matching pop.
- stack.Add(ImRaii.PushColor(ImGuiCol.Button, ColourUtil.RgbaToAbgr(AccentRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.ButtonHovered, ColourUtil.RgbaToAbgr(AccentHoverRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.ButtonActive, ColourUtil.RgbaToAbgr(AccentActiveRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.FrameBg, ColourUtil.RgbaToAbgr(FrameBgRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.FrameBgHovered, ColourUtil.RgbaToAbgr(FrameBgHoverRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.FrameBgActive, ColourUtil.RgbaToAbgr(FrameBgActiveRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.Border, ColourUtil.RgbaToAbgr(BorderRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.Header, ColourUtil.RgbaToAbgr(HeaderRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.HeaderHovered, ColourUtil.RgbaToAbgr(HeaderHoverRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.HeaderActive, ColourUtil.RgbaToAbgr(HeaderActiveRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.TitleBgActive, ColourUtil.RgbaToAbgr(TitleBgActiveRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.CheckMark, ColourUtil.RgbaToAbgr(CheckMarkRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.SliderGrab, ColourUtil.RgbaToAbgr(SliderGrabRgba)));
- stack.Add(ImRaii.PushColor(ImGuiCol.SliderGrabActive, ColourUtil.RgbaToAbgr(SliderGrabActiveRgba)));
+ stack.PushColor(ImGuiCol.Button, PrimaryRgba);
+ stack.PushColor(ImGuiCol.ButtonHovered, PrimaryHoverRgba);
+ stack.PushColor(ImGuiCol.ButtonActive, PrimaryActiveRgba);
+ stack.PushColor(ImGuiCol.FrameBg, FrameBgRgba);
+ stack.PushColor(ImGuiCol.FrameBgHovered, FrameBgHoverRgba);
+ stack.PushColor(ImGuiCol.FrameBgActive, FrameBgActiveRgba);
+ stack.PushColor(ImGuiCol.Border, BorderRgba);
+ stack.PushColor(ImGuiCol.Header, HeaderRgba);
+ stack.PushColor(ImGuiCol.HeaderHovered, HeaderHoverRgba);
+ stack.PushColor(ImGuiCol.HeaderActive, HeaderActiveRgba);
+ stack.PushColor(ImGuiCol.CheckMark, PrimaryRgba);
+ stack.PushColor(ImGuiCol.SliderGrab, PrimaryRgba);
+ stack.PushColor(ImGuiCol.SliderGrabActive, PrimaryHoverRgba);
+ return stack;
+ }
+
+ ///
+ /// Global color and style-variable stack pushed once per frame in
+ /// Plugin.Draw. Covers every ImGui surface the plugin renders so the
+ /// Hellion look is consistent across upstream and Hellion tabs.
+ ///
+ internal static IDisposable PushGlobal()
+ {
+ var stack = new StackHandle();
+
+ // Layout — geometric edges, modest rounding, single-pixel borders.
+ stack.PushStyleVar(ImGuiStyleVar.WindowRounding, 4f);
+ stack.PushStyleVar(ImGuiStyleVar.ChildRounding, 3f);
+ stack.PushStyleVar(ImGuiStyleVar.PopupRounding, 3f);
+ stack.PushStyleVar(ImGuiStyleVar.FrameRounding, 2f);
+ stack.PushStyleVar(ImGuiStyleVar.GrabRounding, 2f);
+ stack.PushStyleVar(ImGuiStyleVar.TabRounding, 2f);
+ stack.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, 2f);
+ stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 1f);
+ stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);
+
+ // Surfaces.
+ stack.PushColor(ImGuiCol.WindowBg, WindowBgRgba);
+ stack.PushColor(ImGuiCol.ChildBg, ChildBgRgba);
+ stack.PushColor(ImGuiCol.PopupBg, PopupBgRgba);
+ stack.PushColor(ImGuiCol.Border, BorderRgba);
+ stack.PushColor(ImGuiCol.BorderShadow, BorderShadowRgba);
+
+ // Frames (input fields, combos, sliders).
+ stack.PushColor(ImGuiCol.FrameBg, FrameBgRgba);
+ stack.PushColor(ImGuiCol.FrameBgHovered, FrameBgHoverRgba);
+ stack.PushColor(ImGuiCol.FrameBgActive, FrameBgActiveRgba);
+
+ // Title bars — tertiary identity on active.
+ stack.PushColor(ImGuiCol.TitleBg, TitleBgRgba);
+ stack.PushColor(ImGuiCol.TitleBgActive, TitleBgActiveRgba);
+ stack.PushColor(ImGuiCol.TitleBgCollapsed, TitleBgCollapsedRgba);
+
+ // Buttons — primary cyan.
+ stack.PushColor(ImGuiCol.Button, PrimaryRgba);
+ stack.PushColor(ImGuiCol.ButtonHovered, PrimaryHoverRgba);
+ stack.PushColor(ImGuiCol.ButtonActive, PrimaryActiveRgba);
+
+ // Headers / selectables — slate with subtle steps.
+ stack.PushColor(ImGuiCol.Header, HeaderRgba);
+ stack.PushColor(ImGuiCol.HeaderHovered, HeaderHoverRgba);
+ stack.PushColor(ImGuiCol.HeaderActive, HeaderActiveRgba);
+
+ // Tabs — tertiary identity for the active tab.
+ stack.PushColor(ImGuiCol.Tab, TabRgba);
+ stack.PushColor(ImGuiCol.TabHovered, TabHoveredRgba);
+ stack.PushColor(ImGuiCol.TabActive, TabActiveRgba);
+ stack.PushColor(ImGuiCol.TabUnfocused, TabUnfocusedRgba);
+ stack.PushColor(ImGuiCol.TabUnfocusedActive, TabUnfocusedActiveRgba);
+
+ // Scrollbar.
+ stack.PushColor(ImGuiCol.ScrollbarBg, ScrollbarBgRgba);
+ stack.PushColor(ImGuiCol.ScrollbarGrab, ScrollbarGrabRgba);
+ stack.PushColor(ImGuiCol.ScrollbarGrabHovered, ScrollbarGrabHoveredRgba);
+ stack.PushColor(ImGuiCol.ScrollbarGrabActive, ScrollbarGrabActiveRgba);
+
+ // Resize grip — secondary amber on active.
+ stack.PushColor(ImGuiCol.ResizeGrip, ResizeGripRgba);
+ stack.PushColor(ImGuiCol.ResizeGripHovered, ResizeGripHoveredRgba);
+ stack.PushColor(ImGuiCol.ResizeGripActive, ResizeGripActiveRgba);
+
+ // Check mark + slider grab — primary cyan.
+ stack.PushColor(ImGuiCol.CheckMark, PrimaryRgba);
+ stack.PushColor(ImGuiCol.SliderGrab, PrimaryRgba);
+ stack.PushColor(ImGuiCol.SliderGrabActive, PrimaryHoverRgba);
+
+ // Separator — primary cyan when hovered/active so the eye
+ // immediately sees that splitters are interactive.
+ stack.PushColor(ImGuiCol.Separator, BorderRgba);
+ stack.PushColor(ImGuiCol.SeparatorHovered, PrimaryHoverRgba);
+ stack.PushColor(ImGuiCol.SeparatorActive, PrimaryRgba);
+
return stack;
}
private sealed class StackHandle : IDisposable
{
- private readonly List _items = new(16);
+ private readonly List _items = new(64);
- internal void Add(IDisposable d) => _items.Add(d);
+ internal void PushColor(ImGuiCol slot, uint rgba)
+ => _items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba)));
+
+ internal void PushStyleVar(ImGuiStyleVar var, float value)
+ => _items.Add(ImRaii.PushStyle(var, value));
public void Dispose()
{
- // Pop in reverse order so the ImGui stack unwinds cleanly.
for (var i = _items.Count - 1; i >= 0; i--)
_items[i].Dispose();
_items.Clear();
diff --git a/ChatTwo/Ui/SettingsTabs/Privacy.cs b/ChatTwo/Ui/SettingsTabs/Privacy.cs
index 7267cc4..17242ae 100644
--- a/ChatTwo/Ui/SettingsTabs/Privacy.cs
+++ b/ChatTwo/Ui/SettingsTabs/Privacy.cs
@@ -66,12 +66,23 @@ internal sealed class Privacy : ISettingsTab
public void Draw(bool changed)
{
- using var _style = HellionStyle.Push();
-
if (ImGui.Button(HellionStrings.Wizard_Reopen_Button))
Plugin.FirstRunWizard.IsOpen = true;
ImGui.Spacing();
+ ImGui.TextUnformatted(HellionStrings.Theme_Heading);
+ using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
+ {
+ ImGuiUtil.OptionCheckbox(
+ ref Mutable.HellionThemeEnabled,
+ HellionStrings.Theme_Enabled_Name,
+ HellionStrings.Theme_Enabled_Description);
+ }
+
+ ImGui.Spacing();
+ ImGui.Separator();
+ ImGui.Spacing();
+
ImGuiUtil.OptionCheckbox(
ref Mutable.PrivacyFilterEnabled,
HellionStrings.Privacy_FilterEnabled_Name,