737ec6b868
The chat log content area is rendered as a child window, so leaving ChildBg opaque kept the inner pane fully solid even when the slider was dragged down — defeating the point during combat where the user wants to see the game behind the chat. Mix the same alpha byte into ChildBg now. FrameBg and PopupBg are deliberately left opaque so text inputs, dropdowns, tooltips and modals stay readable on top.
215 lines
9.8 KiB
C#
215 lines
9.8 KiB
C#
using ChatTwo.Util;
|
||
using Dalamud.Bindings.ImGui;
|
||
using Dalamud.Interface.Utility.Raii;
|
||
|
||
namespace ChatTwo.Ui;
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
internal static class HellionStyle
|
||
{
|
||
// 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;
|
||
|
||
// 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.
|
||
|
||
/// <summary>
|
||
/// Local color stack for Hellion-only surfaces. Cheap. Use inside a
|
||
/// `using var _ = HellionStyle.Push();` block.
|
||
/// </summary>
|
||
internal static IDisposable Push()
|
||
{
|
||
var stack = new StackHandle();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
/// <param name="windowOpacity">Window background alpha (0.5–1.0). Lower
|
||
/// values let the game shine through the plugin panes.</param>
|
||
internal static IDisposable PushGlobal(float windowOpacity = 1.0f)
|
||
{
|
||
var stack = new StackHandle();
|
||
|
||
// Mix the configured opacity into both the outer window and the
|
||
// inner content child backgrounds — without ChildBg following the
|
||
// slider the chat log stays opaque inside even when the user
|
||
// wants to see the game behind it during combat. Form fields and
|
||
// popups (FrameBg, PopupBg) still stay opaque so input is readable.
|
||
var alphaByte = (uint)Math.Clamp((int)(windowOpacity * 255f), 0x55, 0xFF);
|
||
var windowBgWithAlpha = (WindowBgRgba & 0xFFFFFF00u) | alphaByte;
|
||
var childBgWithAlpha = (ChildBgRgba & 0xFFFFFF00u) | alphaByte;
|
||
|
||
// 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, windowBgWithAlpha);
|
||
stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha);
|
||
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<IDisposable> _items = new(64);
|
||
|
||
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()
|
||
{
|
||
for (var i = _items.Count - 1; i >= 0; i--)
|
||
_items[i].Dispose();
|
||
_items.Clear();
|
||
}
|
||
}
|
||
}
|