From c1f1c0563ca18f22ad638ccd55647959bc012bb2 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Tue, 18 Aug 2026 18:05:11 +0200 Subject: [PATCH] fix(style): tint the surface instead of repainting it Both remaining complaints had one cause. The window paints its own background, and the backdrop was filling the same area a second time. Two stacked layers turn a translucent window solid, which is the density over the chat log. And an opaque fill has to carry the entire gradient by itself. That is where the banding came from: a shallow ramp across an opaque surface crosses so few 8-bit values that each one covers a visible stripe. Nothing sits underneath to break them up. So it tints now. Near-transparent white at the top, near-transparent black at the bottom, straight over whatever the window already drew. The window colour stays visible, the ramp only shades it, and the game showing through disperses what little stepping is left. The sidebar does the same with a flat black wash rather than a repainted darker surface. Motes are roughly half as bright everywhere, and the chat log takes 10% of that. DrawVerticalGradient stays for the segmented control, which paints a surface that genuinely is its own rather than one already drawn underneath. --- .../Ui/Components/Settings/TabSidebar.cs | 16 ++++----- .../Ui/StyleEngine/AmbientParticles.cs | 4 +-- HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs | 36 +++++++++---------- HellionChat/Ui/Windows/MainWindow.cs | 2 +- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/HellionChat/Ui/Components/Settings/TabSidebar.cs b/HellionChat/Ui/Components/Settings/TabSidebar.cs index 15ea805..e8237ea 100644 --- a/HellionChat/Ui/Components/Settings/TabSidebar.cs +++ b/HellionChat/Ui/Components/Settings/TabSidebar.cs @@ -38,18 +38,14 @@ internal sealed class TabSidebar var min = ImGui.GetWindowPos(); var max = min + ImGui.GetWindowSize(); - var surface = ColourUtil.RgbaToAbgr(_resolver.Resolve(Token.SurfaceBase, colors)); var accent = ColourUtil.RgbaToAbgr(_resolver.Resolve(Token.AccentPrimary, colors)); - // Darker than the content pane so the two read as separate planes rather - // than one wide surface with a line drawn down it. - dl.DrawVerticalGradient( - min, - max, - ColourUtil.LerpTowardBlack(surface, 0.25f), - topLift: 0.10f, - bottomDrop: 0.06f - ); + // A wash of black rather than a repainted surface. The window has drawn + // its own background already; filling over it stacks a second layer and + // turns a translucent window solid. This only needs to read as the + // darker of two planes, and a tint does that. + var opacity = ((ImGui.GetColorU32(ImGuiCol.WindowBg) >> 24) & 0xFFu) / 255f; + dl.AddRectFilled(min, max, (uint)(0x38 * opacity) << 24); // The icon font has no ASCII glyphs, so anything textual drawn inside a // FontAwesome scope comes out blank -- twice bitten in this plugin. The diff --git a/HellionChat/Ui/StyleEngine/AmbientParticles.cs b/HellionChat/Ui/StyleEngine/AmbientParticles.cs index ccc25ad..387f9c4 100644 --- a/HellionChat/Ui/StyleEngine/AmbientParticles.cs +++ b/HellionChat/Ui/StyleEngine/AmbientParticles.cs @@ -104,13 +104,13 @@ internal sealed class AmbientParticles dl.AddCircleFilled( pos, radius * 2.1f, - ColourUtil.ApplyAlpha(accentAbgr, 0.13f * edge * pulse * intensity), + ColourUtil.ApplyAlpha(accentAbgr, 0.075f * edge * pulse * intensity), 10 ); dl.AddCircleFilled( pos, radius, - ColourUtil.ApplyAlpha(accentAbgr, 0.42f * edge * pulse * intensity), + ColourUtil.ApplyAlpha(accentAbgr, 0.20f * edge * pulse * intensity), 10 ); } diff --git a/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs b/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs index d62d5c7..c833fae 100644 --- a/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs +++ b/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs @@ -35,37 +35,37 @@ internal sealed class SurfaceBackdrop var max = min + ImGui.GetWindowSize(); var colors = _themes.Active.Colors; - // Opacity taken from the window's own background colour rather than - // assumed. BgAlpha only reaches WindowBg, so a draw-list fill painted at - // full opacity turns a deliberately translucent window into a solid - // block -- which is exactly how the settings pane ended up looking like - // a foreign object next to the chat window. - var windowAlpha = ((ImGui.GetColorU32(ImGuiCol.WindowBg) >> 24) & 0xFFu) / 255f; + // Modulation, not a second ground. The window has already painted its + // own background; filling the same area again stacked two layers and + // made a deliberately translucent window read as solid. Worse, an opaque + // fill had to carry the whole gradient by itself, which is what produced + // the banding -- a shallow ramp across an opaque surface crosses few + // enough 8-bit values that each covers a visible stripe. + // + // Near-transparent white over black instead: the window colour stays + // visible underneath, the ramp only tints it, and the game showing + // through breaks up any step that is left. + var opacity = ((ImGui.GetColorU32(ImGuiCol.WindowBg) >> 24) & 0xFFu) / 255f; + var lift = 0x00FFFFFFu | ((uint)(0x0E * opacity) << 24); + var drop = 0x00000000u | ((uint)(0x1A * opacity) << 24); + dl.AddRectFilledMultiColor(min, max, lift, lift, drop, drop); - var surface = ColourUtil.RgbaToAbgr(_resolver.Resolve(Token.SurfaceBase, colors)); if (darken > 0f) - surface = ColourUtil.LerpTowardBlack(surface, darken); - surface = ColourUtil.ApplyAlpha(surface, windowAlpha); + dl.AddRectFilled(min, max, (uint)(0xFF * darken * opacity) << 24); var accent = ColourUtil.RgbaToAbgr(_resolver.Resolve(Token.AccentPrimary, colors)); - // Shallower than it was. The banding people see in a large gradient is - // 8-bit quantisation: a gentle ramp crosses few enough values that each - // one covers a visible band of pixels. Less range means fewer, fainter - // steps, and the window's translucency breaks up what is left. - dl.DrawVerticalGradient(min, max, surface, topLift: 0.10f, bottomDrop: 0.07f); - if (accentWashHeight > 0f) dl.AddRectFilledMultiColor( min, new Vector2(max.X, min.Y + (max.Y - min.Y) * accentWashHeight), - ColourUtil.ApplyAlpha(accent, 0.09f * windowAlpha), - ColourUtil.ApplyAlpha(accent, 0.045f * windowAlpha), + ColourUtil.ApplyAlpha(accent, 0.07f * opacity), + ColourUtil.ApplyAlpha(accent, 0.035f * opacity), accent & 0x00FFFFFFu, accent & 0x00FFFFFFu ); if (moteIntensity > 0f) - _motes.Draw(dl, min, max, accent, moteIntensity * windowAlpha); + _motes.Draw(dl, min, max, accent, moteIntensity * opacity); } } diff --git a/HellionChat/Ui/Windows/MainWindow.cs b/HellionChat/Ui/Windows/MainWindow.cs index a2e76b1..9ad68c2 100644 --- a/HellionChat/Ui/Windows/MainWindow.cs +++ b/HellionChat/Ui/Windows/MainWindow.cs @@ -389,7 +389,7 @@ internal sealed class MainWindow : Window, IFocusableChatWindow // a settings pane, which is read in glances; a chat log is read // line by line, and anything drifting behind the text competes // with it. What is left is barely a texture. - _backdrop.Draw(accentWashHeight: 0f, moteIntensity: 0.18f); + _backdrop.Draw(accentWashHeight: 0f, moteIntensity: 0.10f); if (_activeTab is not null) _messages.Draw(_activeTab);