reading input¶
goo has no input blob. you read the keyboard yourself, once per frame, and feed the result into your tree the same way you feed any other per-frame state: poll in Tick, mutate a field, and return true on the frames something changed.
the helper for that is Goo.Input.KeyTracker. it wraps the engine's per-key query and hands you a list of every key that was just pressed this frame, plus a snapshot of the modifier keys.
the problem it solves¶
the engine exposes Sandbox.Input.Keyboard.Down( name ), which answers "is this one key down right now" for a single named key. there is no built-in "which keys were just pressed across the whole keyboard this frame". KeyTracker polls a catalog of keys once per frame, remembers what was down last frame, and surfaces the rising edges.
polling each frame¶
hold a KeyTracker in a field, call Poll() once per frame in Tick, then read JustPressed and Modifiers. reset it in OnEnabled so a re-enable starts clean.
readonly KeyTracker _tracker = new() { ReemitHeldOnModifierRise = true };
protected override void OnEnabled()
{
base.OnEnabled();
_tracker.Reset();
}
protected override bool Tick( float dt )
{
_tracker.Poll();
bool changed = false;
foreach ( var key in _tracker.JustPressed )
{
_lastKey = key.DisplayName; // any field your tree reads
changed = true;
}
return changed;
}
returning true only on the frames a key actually fired keeps rebuilds gated to real changes, the same Tick contract build method covers for continuous motion. (Rebuild() still works for mutations outside Tick, but a per-frame poll belongs on the Tick clock, not on a manual-Rebuild() loop.) building a keystroke hud works through a full capstone on this same poll-each-frame loop: a queued, animated stack of chips.
what you get back¶
Poll() updates two members.
JustPressed is an IReadOnlyList<KeyDescriptor>: every key whose state rose from up to down on this frame. it is rebuilt each Poll(), so read it right after polling.
each KeyDescriptor describes one key:
| field | type | what it is |
|---|---|---|
| EngineName | string | the name Sandbox.Input.Keyboard.Down accepts (lowercase for modifiers and f1..f12, uppercase for letters) |
| Class | KeyClass | Modifier, Printable, or Special |
| BaseGlyph | char | unshifted character, '\0' for non-printable keys |
| ShiftGlyph | char | shifted character, '\0' for non-printable keys |
| DisplayName | string | human label, e.g. "Ctrl", "Esc", "F1" |
Modifiers is a ModifierState snapshot of the four modifier keys:
| member | type | what it is |
|---|---|---|
| Ctrl | bool | control held |
| Shift | bool | shift held |
| Alt | bool | alt held |
| Meta | bool | the windows / meta key held |
| AnyNonShift | bool | Ctrl, Alt, or Meta held |
| Any | bool | any of the four held |
when you only care about one key, skip the list walk: Pressed( engineName ) returns true if that named key had a rising edge this frame (just-pressed, not held). call Poll() first, same as the other reads.
the catalog: KnownKeys¶
a default KeyTracker() polls KnownKeys.All, a curated catalog of about eighty keys: the four modifiers, A to Z, the US-layout digits and symbols, common specials (space, enter, arrows, and so on), and F1 to F12. it is not exhaustive. non-US layouts and the numpad are out of scope.
to poll a narrower set, filter KnownKeys.All and pass the result to the constructor:
var tracker = new KeyTracker( KnownKeys.All.Where( k =>
k.EngineName is "uparrow" or "downarrow" or "leftarrow" or "rightarrow" ).ToList() );
this is the arrows-only filter Code/Demo/DocsProbes/DocsEventsInputProbeUI.cs (blessed demo) uses to prove a restricted catalog registers arrows and ignores letters. note the engine names: arrows are the Source bind names uparrow/downarrow/leftarrow/rightarrow, never up/down/left/right (those resolve to no key and silently never fire). filter by DisplayName (Up, Down, Left, Right) if you want the friendly names.
KnownKeys.Shift( baseGlyph, shiftDown ) maps a base glyph to its shifted form on a US layout ('a' to 'A', '1' to '!'), returning the input unchanged when shift is not down.
chords: ReemitHeldOnModifierRise¶
by default ReemitHeldOnModifierRise is false: each key reports its own rising edge and nothing more. that misses a common case. if you hold a letter and then press a modifier, the letter's edge already fired on an earlier frame, so the modifier-down frame reports only the modifier. you wanted a chord.
set ReemitHeldOnModifierRise = true and, on any frame where a modifier rises while a non-modifier key is already held, that held key re-emits as just-pressed. now "hold w, then press ctrl" produces a ctrl + w chord rather than a missed combo. the keystroke visualizer turns this on.
see also¶
- events - the mouse-event side of input, and where to read modifier state during a mouse handler
- build method - the
Rebuild()contract, andTickfor continuous per-frame motion - building a keystroke hud - a queued, animated chip stack built on this same
KeyTrackerpoll loop