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.
This commit is contained in:
2026-08-18 18:05:11 +02:00
parent 8b96ffb2ec
commit c1f1c0563c
4 changed files with 27 additions and 31 deletions
@@ -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
@@ -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
);
}
+18 -18
View File
@@ -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);
}
}
+1 -1
View File
@@ -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);