# `tecs.input`
Keyboard, pointer, touch and gamepad state folded from typed platform events.
Live state survives frame boundaries. Frame edges clear before each sealed host
batch, while latched edges accumulate until the first fixed step consumes
them. Blocking layers move capture and clear pending edges without inventing
releases for controls that remain physically held.
Gamepads reach the same fold through a different door. The keyboard, the
pointer and touch arrive in the host's sealed batch, because the window system
owns them; a gamepad belongs to no window, so `tecs.platform.gamepadbackend`
is polled once a frame by the `tecs.PollGamepads` system `install` adds, and
what it saw becomes the same typed platform events the host would have sent.
That is why `handleEvent` is the only fold in this module: a synthetic gamepad
event from the debug server and a real one from a controller take one path.
Buttons use positional names. `"south"` is the button nearest the player on
every pad, where `"a"` is the one nearest the player on some pads and the one
to its right on others.
```nupp
local pad = state:gamepad(1)
if pad ~= nil and pad.connected then
local moveX = pad:axis("leftX")
if pad:buttonPressed("south") then
print(moveX)
end
end
```
## Device support
Gamepads expose positional buttons, sticks, analog triggers, hot plug, power
state, and rumble. Rumble is conditional: `gilrs` implements force feedback on
`evdev` and XInput but not on `IOKit`, so `Gamepad.rumble` answers false on
macOS and for devices without motors.
Touch is available on the platforms where `winit` reports it: Windows, Linux,
Android, and the web. Touch pressure comes through where the surface measures it
and reads zero where it does not.
The pen is the pointer. `winit` declares no pen event, so a stylus arrives as
the cursor moving and its buttons as mouse buttons, and Tecs reads it that way
rather than inventing a device the layer below does not report. A game that
draws with a stylus reads `pointerX`, `pointerY` and the mouse buttons and gets
what the platform sends.
Pen pressure, tilt, and eraser state are not exposed because `winit` declares no
pen event. Supporting them would require separate `NSEvent`, `WM_POINTER`, and
XInput2 tablet handling.
Gamepad motion sensors, touchpads, trigger rumble, light bars, player indexes,
hardware button labels, device families, device paths, remap events, and
standalone motion sensors are not part of the input surface. A game that wants a
`"cross"` glyph rather than a `"south"` name reads the pad's `name` and chooses
its own art.
## Constructors
### `newInput` _constructor_
```nupp
function newInput(backend: gamepadbackend.Backend?): Input
```
Creates empty input state with the readable base layer.
The state reads no device unless it is given a source. Constructing input
state by hand is what a test and a headless tool do, and neither should
start the platform's device enumeration as a side effect: on macOS that is a
run loop on a thread of its own, and a suite whose result changes when
somebody plugs a controller in is worse than one that reads no controller at
all. `install` is the call that reaches real hardware.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `backend` | `gamepadbackend.Backend?` | the gamepad source, defaulting to no devices at all |
#### Returns
| Type | Description |
| --- | --- |
| `Input` | a standalone input state |
## Types
### `Gamepad` _record_
```nupp
record Gamepad
id: integer
connected: boolean
name: string
guid: string
buttonDown: function(borrows self: Gamepad, button: GamepadButton | integer, layer: Layer?): boolean
buttonPressed: function(borrows self: Gamepad, button: GamepadButton | integer, layer: Layer?): boolean
buttonReleased: function(borrows self: Gamepad, button: GamepadButton | integer, layer: Layer?): boolean
axis: function(borrows self: Gamepad, axis: GamepadAxis | integer, deadzone: number?, layer: Layer?): number
hasButton: function(borrows self: Gamepad, button: GamepadButton | integer): boolean
hasAxis: function(borrows self: Gamepad, axis: GamepadAxis | integer): boolean
power: function(borrows self: Gamepad): (gamepadbackend.PowerState, integer)
rumble: function(borrows self: Gamepad, low: number, high: number, seconds: number): boolean
end
```
One attached gamepad's identity, event state and outputs.
Input owns the object. A retained reference stays safe after the device goes
away: queries answer with neutral values, `rumble` returns false, and
`connected` stays false. A reconnect creates a new object rather than
reviving the old one.
#### Methods
##### `buttonDown`
```nupp
buttonDown: function(borrows self: Gamepad, button: GamepadButton | integer, layer: Layer?): boolean
```
Returns whether a gamepad button is held.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Gamepad` | |
| `button` | `GamepadButton | integer` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
###### Raises
- when the name is not one this module declares
##### `buttonPressed`
```nupp
buttonPressed: function(borrows self: Gamepad, button: GamepadButton | integer, layer: Layer?): boolean
```
Returns whether a gamepad button went down in the active edge tier.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Gamepad` | |
| `button` | `GamepadButton | integer` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
###### Raises
- when the name is not one this module declares
##### `buttonReleased`
```nupp
buttonReleased: function(borrows self: Gamepad, button: GamepadButton | integer, layer: Layer?): boolean
```
Returns whether a gamepad button came up in the active edge tier.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Gamepad` | |
| `button` | `GamepadButton | integer` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
###### Raises
- when the name is not one this module declares
##### `axis`
```nupp
axis: function(borrows self: Gamepad, axis: GamepadAxis | integer, deadzone: number?, layer: Layer?): number
```
Returns an axis reading with the deadzone applied.
A stick runs -1 to 1 and a trigger runs zero to one. An axis no event has
mentioned, and one on a device that has gone away, reads as zero.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Gamepad` | |
| `axis` | `GamepadAxis | integer` | |
| `deadzone` | `number?` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `number` | |
###### Raises
- when the name is not one this module declares
##### `hasButton`
```nupp
hasButton: function(borrows self: Gamepad, button: GamepadButton | integer): boolean
```
Reports whether the device has a button.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Gamepad` | |
| `button` | `GamepadButton | integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
###### Raises
- when the name is not one this module declares
##### `hasAxis`
```nupp
hasAxis: function(borrows self: Gamepad, axis: GamepadAxis | integer): boolean
```
Reports whether the device has an axis.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Gamepad` | |
| `axis` | `GamepadAxis | integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
###### Raises
- when the name is not one this module declares
##### `power`
```nupp
power: function(borrows self: Gamepad): (gamepadbackend.PowerState, integer)
```
Reports what the device says about its power source.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Gamepad` | |
###### Returns
| Type | Description |
| --- | --- |
| `gamepadbackend.PowerState` | |
| `integer` | |
##### `rumble`
```nupp
rumble: function(borrows self: Gamepad, low: number, high: number, seconds: number): boolean
```
Plays one rumble effect, replacing whatever the device was playing.
A zero or negative duration stops the device instead. Force feedback is not
available on every platform, so a false here is an ordinary answer rather
than a failure a game has to handle.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Gamepad` | |
| `low` | `number` | |
| `high` | `number` | |
| `seconds` | `number` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
#### Fields
##### `id`
```nupp
id: integer
```
Read-only. Names the device, and matches the `which` field of every
gamepad platform event about it.
##### `connected`
```nupp
connected: boolean
```
Read-only. Reports true from the moment Input opens the device until it
goes away, and false permanently afterwards.
##### `name`
```nupp
name: string
```
Read-only. Reports the platform's display name for the device, and an
empty string when it reports none. Saved bindings use `guid`.
##### `guid`
```nupp
guid: string
```
Read-only. Reports the stable identity a saved binding matches on, and
an empty string when the platform reports none.
### `GamepadAxis` _type_
```nupp
type GamepadAxis = "leftX" | "leftY" | "rightX" | "rightY" | "leftTrigger" | "rightTrigger"
```
A gamepad axis name.
Compatibility surface: these names are stable. A stick runs -1 to 1 with
`leftY` and `rightY` growing downward, and a trigger runs zero to one.
### `GamepadButton` _type_
```nupp
type GamepadButton = "south"
| "east"
| "west"
| "north"
| "back"
| "guide"
| "start"
| "leftStick"
| "rightStick"
| "leftShoulder"
| "rightShoulder"
| "dpadUp"
| "dpadDown"
| "dpadLeft"
| "dpadRight"
```
A positional gamepad button name.
These are what `gilrs` reports, spelled as Tecs publishes them. They are
positional rather than Xbox-only because the hardware label is not part of
the input contract. Names without a `gilrs` counterpart are not published.
### `Input` _record_
```nupp
record Input
mouseX: number
mouseY: number
mouseDeltaX: number
mouseDeltaY: number
wheelX: number
wheelY: number
wheelTicksX: integer
wheelTicksY: integer
text: string
beginFrame: function(exclusive self: Input): nil
enterFixedPhase: function(exclusive self: Input): nil
exitFixedPhase: function(exclusive self: Input): nil
handleEvent: function(self: Input, event: platformevents.Event): nil
keyDown: function(borrows self: Input, key: string, layer: Layer?): boolean
keyPressed: function(borrows self: Input, key: string, layer: Layer?): boolean
keyReleased: function(borrows self: Input, key: string, layer: Layer?): boolean
modifiers: function(borrows self: Input): integer
mouseDown: function(borrows self: Input, button: MouseButton | integer, layer: Layer?): boolean
mousePressed: function(borrows self: Input, button: MouseButton | integer, layer: Layer?): boolean
mouseReleased: function(borrows self: Input, button: MouseButton | integer, layer: Layer?): boolean
pushLayer: function(exclusive self: Input, name: string, blocking: boolean?): Layer
popLayer: function(exclusive self: Input): Layer?
topLayer: function(borrows self: Input): Layer
canRead: function(borrows self: Input, layer: Layer?): boolean
touches: function(borrows self: Input, layer: Layer?): {Touch}
gamepads: function(borrows self: Input): {Gamepad}
gamepad: function(borrows self: Input, index: integer?): Gamepad?
gamepadById: function(borrows self: Input, id: integer): Gamepad?
refreshDevices: function(self: Input): nil
pollDevices: function(exclusive self: Input): {platformevents.Event}
destroy: function(exclusive self: Input): nil
end
```
Live, frame-edge, and fixed-latched keyboard and pointer input.
#### Methods
##### `beginFrame`
```nupp
beginFrame: function(exclusive self: Input): nil
```
Starts a frame by clearing frame-local edges and accumulated deltas.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `enterFixedPhase`
```nupp
enterFixedPhase: function(exclusive self: Input): nil
```
Selects latched edges for queries inside one fixed step.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `exitFixedPhase`
```nupp
exitFixedPhase: function(exclusive self: Input): nil
```
Leaves fixed-edge mode and consumes the latched edges.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `handleEvent`
```nupp
handleEvent: function(self: Input, event: platformevents.Event): nil
```
Folds one translated platform event into live and edge state.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Input` | |
| `event` | `platformevents.Event` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `keyDown`
```nupp
keyDown: function(borrows self: Input, key: string, layer: Layer?): boolean
```
Returns whether a physical key is held.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `key` | `string` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `keyPressed`
```nupp
keyPressed: function(borrows self: Input, key: string, layer: Layer?): boolean
```
Returns whether a physical key went down in the active edge tier.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `key` | `string` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `keyReleased`
```nupp
keyReleased: function(borrows self: Input, key: string, layer: Layer?): boolean
```
Returns whether a physical key came up in the active edge tier.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `key` | `string` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `modifiers`
```nupp
modifiers: function(borrows self: Input): integer
```
Returns the modifier mask from the most recent key event.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `mouseDown`
```nupp
mouseDown: function(borrows self: Input, button: MouseButton | integer, layer: Layer?): boolean
```
Returns whether a pointer button is held.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `button` | `MouseButton | integer` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `mousePressed`
```nupp
mousePressed: function(borrows self: Input, button: MouseButton | integer, layer: Layer?): boolean
```
Returns whether a pointer button went down in the active edge tier.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `button` | `MouseButton | integer` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `mouseReleased`
```nupp
mouseReleased: function(borrows self: Input, button: MouseButton | integer, layer: Layer?): boolean
```
Returns whether a pointer button came up in the active edge tier.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `button` | `MouseButton | integer` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `pushLayer`
```nupp
pushLayer: function(exclusive self: Input, name: string, blocking: boolean?): Layer
```
Pushes a capture layer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Input` | |
| `name` | `string` | |
| `blocking` | `boolean?` | |
###### Returns
| Type | Description |
| --- | --- |
| `Layer` | |
###### Raises
- when the layer name is empty
##### `popLayer`
```nupp
popLayer: function(exclusive self: Input): Layer?
```
Removes and returns the top layer, preserving the base layer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `Layer?` | |
##### `topLayer`
```nupp
topLayer: function(borrows self: Input): Layer
```
Returns the top input layer.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `Layer` | |
##### `canRead`
```nupp
canRead: function(borrows self: Input, layer: Layer?): boolean
```
Reports whether a layer may read through the current capture stack.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `touches`
```nupp
touches: function(borrows self: Input, layer: Layer?): {Touch}
```
Returns every finger currently on a touch surface.
The list is freshly built, and the records in it are Input's and are reused
as the finger moves, so a caller retaining one sees it change and a caller
wanting the position it had copies the numbers out.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `layer` | `Layer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `{Touch}` | |
##### `gamepads`
```nupp
gamepads: function(borrows self: Input): {Gamepad}
```
Returns the connected gamepads, in connection order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `{Gamepad}` | |
##### `gamepad`
```nupp
gamepad: function(borrows self: Input, index: integer?): Gamepad?
```
Returns one connected gamepad by connection order.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `index` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `Gamepad?` | |
##### `gamepadById`
```nupp
gamepadById: function(borrows self: Input, id: integer): Gamepad?
```
Returns one connected gamepad by device identifier.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows self` | `Input` | |
| `id` | `integer` | |
###### Returns
| Type | Description |
| --- | --- |
| `Gamepad?` | |
##### `refreshDevices`
```nupp
refreshDevices: function(self: Input): nil
```
Reconciles the device list against what the platform currently reports.
Engine-owned. Hot plug arrives as events, so this is the safety net for a
stream that was not being drained rather than the ordinary path. It changes
the device list without emitting the events an ordinary attach produces,
because the world it would emit into is not this call's to reach.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
##### `pollDevices`
```nupp
pollDevices: function(exclusive self: Input): {platformevents.Event}
```
Reads the gamepad source and folds what it saw into input state.
Engine-owned. The `tecs.PollGamepads` system `install` adds calls this once
a frame and dispatches what comes back, so ordinary game code reads
`gamepads` instead. A caller driving input without a world calls it directly
and may ignore the result.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `{platformevents.Event}` | |
##### `destroy`
```nupp
destroy: function(exclusive self: Input): nil
```
Retires every device and forgets every finger.
Engine-owned. A host tearing a world down early calls this; ordinary game
code must not. `tecs.application` does not call it, and nothing leaks when
nobody does: the state dies with its world, and the process-wide gamepad
source is deliberately left open here because another world may still be
reading it.
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive self` | `Input` | |
###### Returns
| Type | Description |
| --- | --- |
| `nil` | |
#### Fields
##### `mouseX`
```nupp
mouseX: number
```
##### `mouseY`
```nupp
mouseY: number
```
##### `mouseDeltaX`
```nupp
mouseDeltaX: number
```
##### `mouseDeltaY`
```nupp
mouseDeltaY: number
```
##### `wheelX`
```nupp
wheelX: number
```
##### `wheelY`
```nupp
wheelY: number
```
##### `wheelTicksX`
```nupp
wheelTicksX: integer
```
##### `wheelTicksY`
```nupp
wheelTicksY: integer
```
##### `text`
```nupp
text: string
```
### `Layer` _record_
```nupp
record Layer
name: string
blocking: boolean
index: integer
end
```
A position in the input capture stack.
#### Fields
##### `name`
```nupp
name: string
```
##### `blocking`
```nupp
blocking: boolean
```
##### `index`
```nupp
index: integer
```
### `MouseButton` _type_
```nupp
type MouseButton = "left" | "middle" | "right" | "back" | "forward"
```
A conventional pointer button name.
These are `winit`'s names. `back` and `forward` identify the two auxiliary
pointer buttons.
### `Touch` _record_
```nupp
record Touch
device: string
finger: string
x: number
y: number
normalX: number
normalY: number
pressure: number
end
```
One finger on a touch surface, from the most recent touch event.
Input owns and reuses every field. Callers treat the record as read-only and
copy values they need to retain.
#### Fields
##### `device`
```nupp
device: string
```
Read-only. Names the touch surface's opaque identity, set when the
finger first appears.
##### `finger`
```nupp
finger: string
```
Read-only. Names the finger's opaque identity, set when it first
appears.
##### `x`
```nupp
x: number
```
Read-only. Reports the position in logical window coordinates.
##### `y`
```nupp
y: number
```
Read-only. Reports the position in logical window coordinates.
##### `normalX`
```nupp
normalX: number
```
Read-only. Reports the position across the surface, from zero to one.
##### `normalY`
```nupp
normalY: number
```
Read-only. Reports the position down the surface, from zero to one.
##### `pressure`
```nupp
pressure: number
```
Read-only. Reports the pressure from zero to one, and zero where the
surface does not measure it.
## Functions
### `install` _function_
```nupp
function install(exclusive world: ecs.World, backend: gamepadbackend.Backend?): Input
```
Installs and returns the input state owned by one world.
Three systems come with it. Two consume the fixed-step latched edges, and
`tecs.PollGamepads` runs in `Ingress`, ahead of everything a game schedules,
so a controller read anywhere in the frame answers from this frame.
This is the call that reaches real hardware. The platform's device source is
opened once per process and shared, because opening it per world would give
one controller two identities on two hot-plug streams; a build with no
`libtecsgamepad` gets the device-free source and says so once in the log.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive world` | `ecs.World` | the world whose fixed steps consume latched edges |
| `backend` | `gamepadbackend.Backend?` | the gamepad source, defaulting to the process-wide platform one |
#### Returns
| Type | Description |
| --- | --- |
| `Input` | the new input state, also available through `of` |
#### Raises
- when the world already owns input state
### `of` _function_
```nupp
function of(borrows world: ecs.World): Input?
```
Returns the input state installed in a world.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world to inspect |
#### Returns
| Type | Description |
| --- | --- |
| `Input?` | its input state, or nil when none is installed |