feat(themes): add ThemeAbgrCacheLerp pure-helper for crossfade

Per-slot ABGR byte-lerp between two cache value-records, stack-allocated
output, t clamped. Pattern anchor: imgui.cpp ImAlphaBlendColors.
This commit is contained in:
2026-05-20 08:57:33 +02:00
parent 35e8d3a7fe
commit 8dade8c4b2
+61
View File
@@ -0,0 +1,61 @@
namespace HellionChat.Themes;
// Per-slot ABGR byte-lerp between two ThemeAbgrCache value-records.
// Pattern anchor: imgui.cpp:2820-2828 ImAlphaBlendColors -- decompose
// each byte, lerp via Math.Round, recompose. Stack-allocated output
// (readonly record struct), no heap pressure inside the crossfade
// window. t is clamped to [0, 1] so float drift cannot overshoot.
internal static class ThemeAbgrCacheLerp
{
public static ThemeAbgrCache Lerp(ThemeAbgrCache from, ThemeAbgrCache to, float t)
{
t = Math.Clamp(t, 0f, 1f);
return new ThemeAbgrCache(
PrimaryDark: LerpAbgr(from.PrimaryDark, to.PrimaryDark, t),
Primary: LerpAbgr(from.Primary, to.Primary, t),
PrimaryLight: LerpAbgr(from.PrimaryLight, to.PrimaryLight, t),
PrimaryGlow: LerpAbgr(from.PrimaryGlow, to.PrimaryGlow, t),
AccentDark: LerpAbgr(from.AccentDark, to.AccentDark, t),
Accent: LerpAbgr(from.Accent, to.Accent, t),
AccentLight: LerpAbgr(from.AccentLight, to.AccentLight, t),
Identity: LerpAbgr(from.Identity, to.Identity, t),
WindowBg: LerpAbgr(from.WindowBg, to.WindowBg, t),
ChildBg: LerpAbgr(from.ChildBg, to.ChildBg, t),
FrameBg: LerpAbgr(from.FrameBg, to.FrameBg, t),
Surface: LerpAbgr(from.Surface, to.Surface, t),
SurfaceHover: LerpAbgr(from.SurfaceHover, to.SurfaceHover, t),
Border: LerpAbgr(from.Border, to.Border, t),
TextPrimary: LerpAbgr(from.TextPrimary, to.TextPrimary, t),
TextMuted: LerpAbgr(from.TextMuted, to.TextMuted, t),
TextDim: LerpAbgr(from.TextDim, to.TextDim, t),
StatusSuccess: LerpAbgr(from.StatusSuccess, to.StatusSuccess, t),
StatusDanger: LerpAbgr(from.StatusDanger, to.StatusDanger, t),
StatusWarning: LerpAbgr(from.StatusWarning, to.StatusWarning, t),
StatusInfo: LerpAbgr(from.StatusInfo, to.StatusInfo, t)
);
}
private static uint LerpAbgr(uint from, uint to, float t)
{
var ra = (byte)(from & 0xFFu);
var ga = (byte)((from >> 8) & 0xFFu);
var ba = (byte)((from >> 16) & 0xFFu);
var aa = (byte)((from >> 24) & 0xFFu);
var rb = (byte)(to & 0xFFu);
var gb = (byte)((to >> 8) & 0xFFu);
var bb = (byte)((to >> 16) & 0xFFu);
var ab = (byte)((to >> 24) & 0xFFu);
// Math.Round (default ToEven) matches ColourUtil.ApplyAlpha so a
// crossfade-into-hover transition does not produce a one-byte
// jump at the midpoint between the two paths.
var r = (byte)Math.Round(ra + (rb - ra) * t);
var g = (byte)Math.Round(ga + (gb - ga) * t);
var b = (byte)Math.Round(ba + (bb - ba) * t);
var a = (byte)Math.Round(aa + (ab - aa) * t);
return ((uint)a << 24) | ((uint)b << 16) | ((uint)g << 8) | r;
}
}