fix(style): drive the hover sheen from held state, drop the leaking start map
The sheen kept its own Dictionary<string, DateTime> of start timestamps and
only cleared an entry in the un-hover branch. A row that disappeared while the
pointer was on it left its entry behind until the plugin reloaded, which is
exactly what happens to temp tabs under the LRU limit.
It now takes the held intensity from HoverState and draws on the rising edge
only. The alpha falls off as the value climbs, so the sweep has faded out by
the time the surface underneath is fully in. On the way out it simply does not
run, which is what stops it from travelling backwards -- the old
SheenStarts.Remove prevented that by resetting, and dropping the map without
this guard would have reintroduced it.
The sidebar call site built "sidebar.tab.{guid}" per row per frame, two
allocations each. Master spec 5.3 asks for constant keys, and 7.5 for a stable
allocation count. It now uses ImGui.GetID("row"u8), which is allocation-free
and seeded from the window's ID stack, so the same literal stays distinct per
window and per PushID'd tab.
HoverSheenAllocStep pinned the old dictionary contract and is replaced by
HoverStateFootprintStep, which pins the same property against the registry:
repeated queries add no entries, and an element that stops being queried leaves
the map instead of leaking.
This commit is contained in:
@@ -251,11 +251,16 @@ internal sealed class Sidebar
|
||||
TabLifecycleHelpers.OnTabActivated(tab, previous);
|
||||
}
|
||||
|
||||
// GetID is seeded from the window's ID stack, so the same "row" literal
|
||||
// stays distinct per window and per PushID'd tab. The old interpolated
|
||||
// key allocated two strings per row per frame.
|
||||
var hoverId = ImGui.GetID("row"u8);
|
||||
var hoverAmount = StyleEngine.HoverState.Query(hoverId, rowHovered);
|
||||
dl.DrawHoverSheen(
|
||||
origin,
|
||||
origin + new Vector2(avail, RowHeight),
|
||||
accentRgba,
|
||||
$"sidebar.tab.{tab.Identifier}",
|
||||
hoverAmount,
|
||||
rowHovered
|
||||
);
|
||||
|
||||
|
||||
@@ -9,13 +9,9 @@ namespace HellionChat.Ui.StyleEngine;
|
||||
|
||||
// Custom-drawing primitives for the v2.x style layer. Callers feed RGBA
|
||||
// uints (typically resolved via TokenResolver) and these methods convert to
|
||||
// ABGR before delegating to ImDrawList. Hover-sheen state lives in a small
|
||||
// static dictionary keyed by constant strings — keep keys constant and
|
||||
// scope to static UI elements so the per-key footprint stays bounded.
|
||||
// ABGR before delegating to ImDrawList.
|
||||
internal static class DrawListExtensions
|
||||
{
|
||||
private const float SheenDurationSeconds = 0.65f;
|
||||
|
||||
// A1 accent-tint (Variante A): how far the white sweep is pulled toward
|
||||
// the element's accent hue. Kept low so the sheen reads as a tinted
|
||||
// highlight, not a saturated accent flash (effect level "subtle").
|
||||
@@ -24,41 +20,25 @@ internal static class DrawListExtensions
|
||||
// Peak sheen alpha (low so the highlight stays subtle); DrawHoverSheen applies the falloff.
|
||||
private const byte SheenPeakAlpha = 0x40;
|
||||
|
||||
private static readonly Dictionary<string, DateTime> SheenStarts = new();
|
||||
|
||||
// Test-observability hook for HoverSheenAllocStep: lets the self-test
|
||||
// assert the hover/un-hover dictionary contract via real state instead
|
||||
// of an unconditional pass (K7 false-green fix). Not a runtime path.
|
||||
internal static bool IsSheenTracked(string elementId) => SheenStarts.ContainsKey(elementId);
|
||||
|
||||
// Rides the held hover intensity instead of its own timer, so it can no
|
||||
// longer leak an entry when an element disappears while hovered. Drawn on
|
||||
// the rising edge only: the alpha falls off as the value climbs, so the
|
||||
// sweep has faded out by the time the surface underneath is fully in.
|
||||
// On the way out the surface fades and the sweep simply does not run,
|
||||
// which is what keeps it from travelling backwards.
|
||||
public static void DrawHoverSheen(
|
||||
this ImDrawListPtr dl,
|
||||
Vector2 min,
|
||||
Vector2 max,
|
||||
uint accentRgba,
|
||||
string elementId,
|
||||
float intensity,
|
||||
bool hovered
|
||||
)
|
||||
{
|
||||
if (!hovered)
|
||||
{
|
||||
// Reset so re-hover restarts the sweep instead of catching the
|
||||
// tail end of a stale animation.
|
||||
SheenStarts.Remove(elementId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SheenStarts.TryGetValue(elementId, out var started))
|
||||
{
|
||||
started = DateTime.UtcNow;
|
||||
SheenStarts[elementId] = started;
|
||||
}
|
||||
|
||||
var elapsed = (DateTime.UtcNow - started).TotalSeconds;
|
||||
if (elapsed > SheenDurationSeconds)
|
||||
if (!hovered || intensity <= 0f || intensity >= 1f)
|
||||
return;
|
||||
|
||||
var t = (float)(elapsed / SheenDurationSeconds);
|
||||
var t = intensity;
|
||||
var alpha = (byte)Math.Round(SheenPeakAlpha * (1f - t));
|
||||
// Tint the sweep toward the accent hue, then stamp the falloff alpha.
|
||||
// accentRgba is RGBA; convert to ABGR FIRST or the draw-list swaps R/B.
|
||||
|
||||
Reference in New Issue
Block a user