Utility that implements held-key auto-repeat for vertical menu navigation. It tracks current direction, timers for initial delay and repeat rate, emits immediate step on press or reversal, and can suppress further repeats when movement is blocked.
namespace BlockParty;
/// <summary>
/// Shared held-key auto-repeat for vertical menu/list navigation. Fires once the instant a
/// direction is first pressed, then — while it stays held — again after <see cref="RepeatDelay"/>
/// and every <see cref="RepeatRate"/> thereafter. One instance per navigable screen; the tunables
/// here are the single source of truth for how fast holding W/S scrolls anywhere in the UI
/// (the leaderboard lists, the title menu, and the options menu all drive it through this).
/// </summary>
public sealed class NavRepeater
{
// First auto-repeat only after this hold time (so a quick tap is a single step); subsequent
// held steps at this faster cadence.
public const float RepeatDelay = 0.35f;
public const float RepeatRate = 0.065f;
private int _dir; // direction currently held (0 = none)
private float _timer;
private bool _blocked; // set when repeats in the current direction are suppressed (dead end)
/// <summary>
/// Steps to apply this frame: -1 (up), +1 (down), or 0 for none. Call once per frame with the
/// current held up/down state; it emits the initial press and the held repeats. Releasing both
/// (or the frame after a repeat that hasn't re-armed) returns 0.
/// </summary>
public int Tick( float dt, bool up, bool down )
{
int dir = down ? 1 : up ? -1 : 0; // if somehow both are held, down wins
if ( dir == 0 )
{
_dir = 0;
_blocked = false;
return 0;
}
// Fresh press (or a reversal): step immediately and arm the initial delay.
if ( dir != _dir )
{
_dir = dir;
_timer = RepeatDelay;
_blocked = false;
return dir;
}
// Repeats suppressed for this hold (e.g. the last step hit a dead end): stay silent until the
// key is released or the direction reverses (both reset _blocked above).
if ( _blocked )
return 0;
// Same direction still held: step on the repeat cadence.
_timer -= dt;
if ( _timer <= 0f )
{
_timer = RepeatRate;
return dir;
}
return 0;
}
/// <summary>Suppress further auto-repeats in the currently held direction until the key is released
/// or the direction reverses. Call after a repeated step that couldn't move (a dead end / locked
/// neighbour) so holding toward nothing doesn't keep re-firing the wobble/error cue.</summary>
public void SuppressRepeat() => _blocked = true;
}