From 07470f527e1776c6809a41f9b3335077862e3292 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Fri, 1 May 2026 21:13:58 +0200 Subject: [PATCH] Apply Hellion theme plugin-wide with multi-accent palette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move from a local color stack inside Hellion-only surfaces to a single push wrapping Plugin.Draw, so chat log, settings, viewers, the file dialog and the wizard all render under the same palette. The local Push() helper stays for explicit use, but the two existing call sites (Privacy tab, FirstRunWizard) now drop their local pushes — the global stack already covers them and double-pushing would shift colors on every frame. Palette grew from a single cyan accent into a three-tone HUD set: Primary cyan-teal (#00B8D4) → buttons, checkboxes, slider grabs, separator hover/active. Secondary industrial amber → scrollbar grab and resize-grip (#FFB300) hover/active highlights. Tertiary slate violet → active title bars and active tabs (#7B61FF) so identity beats out the cyan accent without competing with it on action controls. Surfaces are deep slate (#0E1A20 windows, #102027 children, #162831 frames) with steel borders (#37474F). Style variables flatten the default Dalamud rounding into something more geometric: 4 px window rounding, 2 px frame/grab/tab/scrollbar, 1 px borders. A new Configuration.HellionThemeEnabled (default true) and a matching Appearance section at the top of the Privacy tab let users turn the whole thing off and fall back to the Dalamud default look. The flag is checked once per frame in Plugin.Draw — `using IDisposable? _ = ... ? PushGlobal() : null` — so disabling has zero overhead beyond a bool check. --- ChatTwo/Configuration.cs | 6 + ChatTwo/Plugin.cs | 6 + ChatTwo/Resources/HellionStrings.Designer.cs | 4 + ChatTwo/Resources/HellionStrings.de.resx | 9 + ChatTwo/Resources/HellionStrings.resx | 9 + ChatTwo/Ui/FirstRunWizard.cs | 2 - ChatTwo/Ui/HellionStyle.cs | 207 +++++++++++++++---- ChatTwo/Ui/SettingsTabs/Privacy.cs | 15 +- 8 files changed, 216 insertions(+), 42 deletions(-) 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,