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 state: mutate a field, call Rebuild().
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 OnUpdate, 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 void OnUpdate()
{
_tracker.Poll();
foreach ( var key in _tracker.JustPressed )
Log.Info( key.DisplayName );
base.OnUpdate();
}
the OnUpdate plus Rebuild() clock is the same one the build method teaches: poll, update your fields, rebuild. the keystroke visualizer demo (Code/Demos/KeystrokeVisualizerUI.cs) sets its tracker up exactly like the field above, then routes JustPressed into its own grouping logic instead of logging.
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, pass your own list to the constructor:
var arrows = new[]
{
KnownKeys.All[0], // or build your own KeyDescriptor list
};
var tracker = new KeyTracker( arrows );
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
OnUpdateplusRebuild()clock that drives per-frame polling