fix(style): the hover fade ran at light speed, and stopped dead
Tester feedback via Flo, aimed at the sidebar tabs but true everywhere: 14/s in means a full fade in seventy milliseconds, which is five frames -- fast enough to read as a flicker rather than a fade. Halved to 6.5/s in and 4/s out, which puts the rise around 150ms and the settle around 250ms, where UI fades usually live. And the curve was linear: one speed the whole way, then a dead stop. Consumers now get a smoothstep over the linear state -- soft start, soft landing -- while the state itself stays linear, because the evict rule and the advance step are written against it. Every hover in the plugin inherits both changes through the one Query call, so the sidebar, the popups, the ghost buttons and the message rows all breathe at the same pace. The timing test now guards the other direction: it fails if anyone drifts the rate back toward light speed.
This commit is contained in:
@@ -61,7 +61,10 @@ internal static class HoverState
|
||||
// OR, never assign: a second caller in the same frame that is not hovered
|
||||
// must not cancel the first one that is.
|
||||
entry.Hovered |= hovered;
|
||||
return entry.Value;
|
||||
|
||||
// Callers get the eased value; the entry keeps the linear one, which is
|
||||
// what the evict rule and the advance step are written against.
|
||||
return HoverMath.Ease(entry.Value);
|
||||
}
|
||||
|
||||
// Never re-entrant: BeginFrame runs once from the draw thread, AdvanceForTest
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
namespace HellionChat.Util;
|
||||
|
||||
// State rules for the held hover value, split from HoverState so the build suite
|
||||
// can pin them without an ImGui frame. Rates follow Lightless (Selune.cs:36-37):
|
||||
// slower out than in is what makes a fade read as deliberate rather than laggy.
|
||||
// can pin them without an ImGui frame. Slower out than in is what makes a fade
|
||||
// read as deliberate rather than laggy (the asymmetry follows Lightless,
|
||||
// Selune.cs:36-37).
|
||||
//
|
||||
// The rates started at 14/s in and 8/s out -- a full fade-in in seventy
|
||||
// milliseconds, which tester feedback called light speed. Half that now: rise in
|
||||
// about 150ms, settle in about 250ms, which is where UI fades usually live.
|
||||
internal static class HoverMath
|
||||
{
|
||||
internal const float FadeInPerSecond = 14f;
|
||||
internal const float FadeOutPerSecond = 8f;
|
||||
internal const float FadeInPerSecond = 6.5f;
|
||||
internal const float FadeOutPerSecond = 4f;
|
||||
|
||||
// Below this an entry is indistinguishable from zero and can be dropped.
|
||||
internal const float EvictBelow = 0.001f;
|
||||
@@ -20,4 +25,9 @@ internal static class HoverMath
|
||||
);
|
||||
|
||||
internal static bool ShouldEvict(float value, bool hovered) => !hovered && value <= EvictBelow;
|
||||
|
||||
// Smoothstep over the linear state. The state itself stays linear -- evict
|
||||
// and threshold logic depend on it -- but what callers paint with starts
|
||||
// gently and lands gently instead of moving at one speed and stopping dead.
|
||||
internal static float Ease(float t) => t * t * (3f - 2f * t);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user