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,