The Eighth Ketyl
README.md
Focus
Menu focus navigation for THE EIGHTH KETYL. A controller has no pointer, so every menu in this game carries an ordered ring of focusable ids and exactly one of them is lit; this module is what walks that ring.
Why it exists. A game published to a television has to be finishable with a
thumbstick alone, and a game published to a phone has to answer a finger on the
same rows. The four families reach one model here — the stick and the d-pad step
the focus, confirm acts on whatever is lit, a mouse crossing a row moves the
focus onto it, and a finger tapping a row does both — so nothing on screen ever
disagrees about what is selected.
The repeat is the part with teeth. A held stick has to walk the list rather
than sprint it, and a flick has to be one step. The delay is measured in seconds
AND in frames: on a machine running long frames a single flick can arrive as one
onInput carrying half a second of dt, and a delay measured only in time turns
that flick into two steps down the menu.
What it provides
Focus.open(list, prefer)— open a menu: the ids in the order the stick walks them, and which one starts lit.Focus.close()/Focus.release()— close it; let go of a held direction.Focus.id()/Focus.isFocused(id)/Focus.items()— read the focus.Focus.axis(v, dt)— feed thenavigateaxis; handles the press edge and the held repeat, and wraps at both ends so a pad never dead-ends.Focus.step(delta)— move by whole items.Focus.point(id)— put the focus on a named item: what a mouse crossing it, or a finger tapping it, does.Focus.generation()— a number that changes each time a menu opens, for a screen that animates its entrance.
Who consumes it
The Screens component, which feeds it the console.inputMap controls, and
ui.Menus, which asks it which row to draw lit. It holds no widgets and reads no
input itself.