diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index a039011..c8fa192 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -379,10 +379,9 @@ internal static class PluginHostFactory sp.GetRequiredService() )); #endif - // Temporary: a side-by-side comparison of input-row variants, so the - // v1.14.0 style decision is made by looking rather than by imagining. - // Deliberately NOT behind DEBUG -- the decision happens in the build Flo - // actually runs. Comes out once the variants are picked. + // The style lab: variants side by side, in-game, against the live theme. + // Permanent by Flo's call, and deliberately not behind DEBUG -- style + // decisions happen in the build he actually runs. services.AddSingleton(sp => new Ui.Windows.InputBarLabWindow( sp.GetRequiredService() )); diff --git a/HellionChat/Ui/Components/InputBar.cs b/HellionChat/Ui/Components/InputBar.cs index 031df50..377c30d 100644 --- a/HellionChat/Ui/Components/InputBar.cs +++ b/HellionChat/Ui/Components/InputBar.cs @@ -182,7 +182,13 @@ internal sealed class InputBar var pillToken = isTell ? Token.AccentEmber : Token.AccentPrimary; var pillRgba = _resolver.Resolve(pillToken, theme.Colors); var pillAbgr = ColourUtil.RgbaToAbgr(pillRgba); - var pillTextAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary); + // Measured against the pill fill it lands on, not taken from the theme + // raw -- an accent fill can swallow the theme's text colour whole. + var pillTextAbgr = ColourUtil.EnsureContrast( + ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary), + pillAbgr, + 4.5f + ); DrawChannelPill(activeTab, isTell, pillAbgr, pillTextAbgr); ImGui.SameLine(); @@ -271,19 +277,18 @@ internal sealed class InputBar var size = StyleEngine.Widgets.Pill.CalcSize(label, withDot: false); var origin = ImGui.GetCursorScreenPos(); - StyleEngine.Widgets.Pill.Draw(origin, label, pillAbgr, textAbgr); - - // The CS+ pill treatment: a faint white gradient from the top and a - // one-pixel light along the upper edge. Reads as depth in every palette - // because it is white over the fill, not a second hue. + // Chamfered, not rounded: the shape the segmented control already uses + // for its selected segment, with the CS+ white gradient for depth. The + // slip corner is the one piece of the Boutique geometry the plugin had + // built and barely used. var pillDl = ImGui.GetWindowDrawList(); var pillScale = StyleEngine.Metrics.Scale; - pillDl.DrawVerticalGradient(origin, origin + size, 0x28FFFFFFu, 0u); - pillDl.AddRectFilled( - new Vector2(origin.X + 6f * pillScale, origin.Y), - new Vector2(origin.X + size.X - 6f * pillScale, origin.Y + 1f * pillScale), - 0x50FFFFFFu - ); + var pillMax = origin + size; + pillDl.DrawSlipPolygon(origin, pillMax, ColourUtil.RgbaToAbgr(pillAbgr), 6f * pillScale); + pillDl.DrawVerticalGradient(origin, pillMax, 0x28FFFFFFu, 0u); + + var labelSize = ImGui.CalcTextSize(label); + pillDl.AddText(origin + (size - labelSize) * 0.5f, textAbgr, label); // Hit area over the rendered pill so a click opens the channel // picker. InvisibleButton both reserves the layout slot and gives @@ -735,16 +740,39 @@ internal sealed class InputBar var c = _themes.Active.Colors; var dl = ImGui.GetWindowDrawList(); + // Every colour here is measured against what it actually lands on. The + // glyph sits on the window floor, the glow hugs the fill -- a raw theme + // accent can vanish on either, which is exactly the warning that came + // back from the smoke test. + var accent = ColourUtil.EnsureContrast( + ColourUtil.RgbaToAbgr(c.Accent), + ColourUtil.RgbaToAbgr(c.ChildBg), + 3f + ); + if (amount > 0f) + { dl.AddRectFilled( origin, max, - ColourUtil.ApplyAlpha(ColourUtil.RgbaToAbgr(c.SurfaceHover), amount), + ColourUtil.ApplyAlpha(ColourUtil.RgbaToAbgr(c.Surface), amount * 0.8f), 3f * scale ); - var accent = ColourUtil.RgbaToAbgr(c.Accent); - var tint = ColourUtil.RgbaToAbgr(ColourUtil.EnsureContrast(c.TextMuted, c.ChildBg, 4.5f)); + // DrawGlowBorder reads RGBA with alpha in the low byte; ApplyAlpha + // writes the high byte (ABGR). Mixing them is the channel-order trap + // this cycle already fell into once, so the alpha byte is set by hand. + var glowRgba = + (ColourUtil.RgbaToAbgr(accent) & 0xFFFFFF00u) + | (uint)(byte)Math.Round(0xB4 * amount); + dl.DrawGlowBorder(min: origin, max: max, glowRgba, 1f, 4); + } + + var tint = ColourUtil.EnsureContrast( + ColourUtil.RgbaToAbgr(c.TextMuted), + ColourUtil.RgbaToAbgr(c.ChildBg), + 4.5f + ); if (lit) tint = accent; else if (amount > 0f) diff --git a/HellionChat/Ui/StyleEngine/Widgets/ChannelHeader.cs b/HellionChat/Ui/StyleEngine/Widgets/ChannelHeader.cs index 6dc720e..c738784 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/ChannelHeader.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/ChannelHeader.cs @@ -16,8 +16,10 @@ namespace HellionChat.Ui.StyleEngine.Widgets; // ones, because its only distinction from its surroundings is hue. Tracking is // shape, and shape survives every palette. // -// So: no plate, and a rule that fades out between the name and the world, the -// same way SectionHeader runs one between its title and the edge. +// So: no plate, but a tint. A tenth-opacity accent wash falling from the top +// edge -- colour as atmosphere rather than as a box, which is what the plate got +// wrong. Flo picked it from the lab over the rule-only variant; at this strength +// it survives the violet themes that killed the filled bar. // // Which face draws what is not a style choice here, it is a constraint. The meta // face has a glyph range of ASCII plus a middle dot, so only the world name and @@ -30,7 +32,6 @@ internal static class ChannelHeader private const float TrackRaw = 1.8f; private const float DetailTrackRaw = 0.9f; private const float IconGapRaw = 8f; - private const float RuleGapRaw = 10f; // Measured, never a fixed 32px: the band has to hold a line of text, and the // font comes from Config, which display scaling does not feed into. @@ -160,19 +161,22 @@ internal static class ChannelHeader var dl = ImGui.GetWindowDrawList(); var bottomRight = origin + new Vector2(width, height); + // Converted first, then measured. EnsureContrast works in ABGR -- + // handing it the theme's RGBA swaps red and blue on both arguments, + // so it measures a contrast that has nothing to do with what ends up + // on screen and returns a colour in the wrong order on top. + var accent = ColourUtil.EnsureContrast( + ColourUtil.RgbaToAbgr(theme.Colors.Accent), + surfaceAbgr, + 4.5f + ); + + dl.DrawEdgeTint(origin, bottomRight, ColourUtil.ApplyAlpha(accent, 0.10f), height); + var textY = origin.Y + MathF.Round(PadYRaw * scale); if (plan.ShowName) { - // Converted first, then measured. EnsureContrast works in ABGR -- - // handing it the theme's RGBA swaps red and blue on both arguments, - // so it measures a contrast that has nothing to do with what ends up - // on screen and returns a colour in the wrong order on top. - var accent = ColourUtil.EnsureContrast( - ColourUtil.RgbaToAbgr(theme.Colors.Accent), - surfaceAbgr, - 4.5f - ); var x = origin.X + inset; // Centred against the band, not aligned to the text baseline: @@ -188,28 +192,13 @@ internal static class ChannelHeader x += iconSize.X + IconGapRaw * scale; - float drawn; using (meta.Push()) - drawn = dl.DrawTrackedText( + dl.DrawTrackedText( new Vector2(x, textY + DropFor(body, meta, scale)), name, accent, track ); - - // Starts where the name ends and fades into nothing, so it reads as a - // continuation of the heading rather than as a box lid. Stops short of - // the trailing detail when there is one. - var ruleX = x + drawn + RuleGapRaw * scale; - var ruleEnd = - bottomRight.X - inset - (plan.ShowDetail ? detailRun + RuleGapRaw * scale : 0f); - if (ruleEnd > ruleX) - dl.DrawFadeRule( - new Vector2(ruleX, textY + ImGui.GetTextLineHeight() * 0.5f), - ruleEnd - ruleX, - ColourUtil.RgbaToAbgr(theme.Colors.Border), - MathF.Max(1f, scale) - ); } if (plan.ShowDetail && detail.Where.Length > 0) diff --git a/HellionChat/Ui/Windows/InputBarLabWindow.cs b/HellionChat/Ui/Windows/InputBarLabWindow.cs index 5f3fc07..b9a8f4c 100644 --- a/HellionChat/Ui/Windows/InputBarLabWindow.cs +++ b/HellionChat/Ui/Windows/InputBarLabWindow.cs @@ -10,11 +10,12 @@ using HellionChat.Util; namespace HellionChat.Ui.Windows; -// Variants of the input row, drawn side by side so a decision can be made by -// looking rather than by imagining. Reachable with /hellion lab. +// Variants of UI elements, drawn side by side so decisions are made by looking +// rather than by imagining. Reachable with /hellion lab. // -// Temporary. It comes out once the variants are picked, which is also why it is -// not behind DEBUG -- the decision happens in the build that actually runs. +// Permanent, by Flo's call: a dev playground for seeing ideas in-game against +// the live theme. The radios default to whatever shipped, so the window also +// documents which variant won and what it beat. // // It exists because the alternative was drawing mockups, and mockups are what // sent this cycle down the wrong path once already: they are from before the @@ -30,10 +31,10 @@ internal sealed class InputBarLabWindow : Window private readonly Plugin _plugin; private string _sample = string.Empty; - private int _buttonVariant; - private int _fieldVariant; - private int _pillVariant; - private int _headerVariant; + private int _buttonVariant = 2; + private int _fieldVariant = 2; + private int _pillVariant = 2; + private int _headerVariant = 2; internal InputBarLabWindow(Plugin plugin) : base("Input Bar Lab###hellion-input-lab") @@ -163,13 +164,12 @@ internal sealed class InputBarLabWindow : Window ColourUtil.ApplyAlpha(ColourUtil.RgbaToAbgr(c.Surface), amount * 0.8f), 3f * scale ); - dl.DrawGlowBorder( - min, - max, - ColourUtil.ApplyAlpha(c.Accent, amount * 0.7f), - 1f, - 4 - ); + // Alpha into the LOW byte: DrawGlowBorder reads RGBA, while + // ApplyAlpha writes the high byte (ABGR). The first version + // of this lab mixed them up and got a full-alpha glow with + // the red channel dimmed instead. + var glow = (c.Accent & 0xFFFFFF00u) | (uint)(byte)Math.Round(0xB4 * amount); + dl.DrawGlowBorder(min, max, glow, 1f, 4); } break; }