simvx.core.input.actions

How device inputs compose into an action, and what type that action has.

A game declares what an action is rather than assembling it at every call site::

input_context = {
    "jump": [Key.SPACE, Controller.A],                              # bool
    "throttle": [Axis1D(low=Key.LEFT_CONTROL, high=Key.SPACE),
                 Controller.RIGHT_TRIGGER],                         # float
    "move": [Axis2D.WASD, Controller.LEFT_STICK],                   # Vec2
}

The type follows from the bindings: buttons alone give bool, an

class:

Axis1D or a single axis gives float, an :class:Axis2D or a stick gives Vec2. Input.value(action) returns that type and Input.pressed(action) still answers on all three, meaning “past the deadzone” for a continuous one.

Each entry in the list is one source, and a source produces a whole value of the action’s type on its own. That is what makes “largest magnitude wins” well defined: the keys give one vector, the stick gives another, and the action takes whichever is further from rest rather than summing them.

A full axis is its two halves, and a stick is its two axes, so there is one composite for a scalar and one for a vector rather than a type per spelling: a bare axis such as Controller.LEFT_X or a tablet’s ring normalises to an

class:

Axis1D over its negative and positive halves, and a stick such as Controller.LEFT_STICK to an :class:Axis2D over the halves of its two axes.

A string is another entry’s name. Actions and device aliases are entries of one map, so "pause": ["start", Key.ESCAPE] takes whatever "start" resolves to, and Axis2D(up="forward", ...) takes the one control "forward" names. The input map expands the names once whenever it changes.

Module Contents

Classes

ActionKind

What type an action’s value has, decided by the bindings it was given.

Axis1D

Two opposed inputs composed into one signed value, -1.0 to 1.0.

Axis2D

Four inputs composed into a direction, with a magnitude of at most 1.0.

Action

One entry of the input map: what it is called, what type it has, what drives it.

Functions

to_source

Normalise one entry of an action’s binding list into a source.

kind_for

The type an action has, given its sources, refusing a list that has none.

Data

API

simvx.core.input.actions.__all__

[‘ActionKind’, ‘Axis1D’, ‘Axis2D’, ‘BindingSpec’, ‘SourceSpec’]

class simvx.core.input.actions.ActionKind[source]

Bases: enum.StrEnum

What type an action’s value has, decided by the bindings it was given.

Initialization

Initialize self. See help(type(self)) for accurate signature.

BUTTON

‘button’

AXIS_1D

‘axis_1d’

AXIS_2D

‘axis_2d’

__new__(*values)
__add__()
__contains__()
__delattr__()
__dir__()
__eq__()
__format__()
__ge__()
__getattribute__()
__getitem__()
__getnewargs__()
__getstate__()
__gt__()
__hash__()
__iter__()
__le__()
__len__()
__lt__()
__mod__()
__mul__()
__ne__()
__reduce__()
__reduce_ex__()
__repr__()
__rmod__()
__rmul__()
__setattr__()
__sizeof__()
__str__()
__subclasshook__()
capitalize()
casefold()
center()
count()
encode()
endswith()
expandtabs()
find()
format()
format_map()
index()
isalnum()
isalpha()
isascii()
isdecimal()
isdigit()
isidentifier()
islower()
isnumeric()
isprintable()
isspace()
istitle()
isupper()
join()
ljust()
lower()
lstrip()
partition()
removeprefix()
removesuffix()
replace()
rfind()
rindex()
rjust()
rpartition()
rsplit()
rstrip()
split()
splitlines()
startswith()
strip()
swapcase()
title()
translate()
upper()
zfill()
__deepcopy__(memo)
__copy__()
name()
value()
class simvx.core.input.actions.Axis1D[source]

Two opposed inputs composed into one signed value, -1.0 to 1.0.

Axis1D(low=Key.LEFT_CONTROL, high=Key.SPACE) reads -1.0 while Control is held, 1.0 while Space is, and 0.0 with neither or both. Either side may be an axis half, which is how a trigger or a stick direction joins the same action: a bare axis in an action’s binding list becomes this over its two halves.

Attributes: low: What drives the value negative. high: What drives it positive. deadzone: How far from rest the composed value must be to count. A component that is an axis half applies it; a button never sits inside it.

low: simvx.core.input.actions.BindingSpec

None

high: simvx.core.input.actions.BindingSpec

None

deadzone: float

0.2

__post_init__() → None[source]
property parts: tuple[simvx.core.input.actions.Part, simvx.core.input.actions.Part][source]

This source’s components as declared, (low, high): bindings, or entries’ names.

property bindings: tuple[simvx.core.input.events.InputBinding, simvx.core.input.events.InputBinding][source]

This source’s components, (low, high), once the map has resolved every name in it.

__mul__(factor: float) → simvx.core.input.actions.Axis1D[source]

Both halves multiplied into the action’s unit: Axis1D(low=Key.Q, high=Key.E) * 2.

__rmul__

None

per_second(rate: float) → simvx.core.input.actions.Axis1D[source]

Both halves as motion: the held value times rate each second.

classmethod for_device_axis(axis: simvx.core.input.controls.DeviceAxis, *, deadzone: float = 0.0) → simvx.core.input.actions.Axis1D[source]

One whole axis, a tablet’s ring or strip, as a signed value: its two halves.

No deadzone by default: a dial reads exactly where it was turned, and a delta of a fraction of a turn is a real motion.

class simvx.core.input.actions.Axis2D[source]

Four inputs composed into a direction, with a magnitude of at most 1.0.

Axis2D(up=Key.W, down=Key.S, left=Key.A, right=Key.D) is the keyboard half of a movement action, ready-made as :attr:WASD and :attr:ARROWS; Controller.LEFT_STICK in the same list is the stick half, and the action takes whichever is further from rest. +y is down the screen, as everywhere else in 2D. Keyword-only, because an order is a convention nothing checks and a wrong one inverts movement silently.

The deadzone is radial rather than per axis, so a stick pushed diagonally is not clipped into a square: the whole vector is discarded when it is shorter than deadzone and passed through when it is not. A diagonal held on the keyboard is normalised to length 1.0, so walking diagonally is not faster.

Attributes: up, down, left, right: What drives each direction. deadzone: How far from centre the composed vector must reach to count.

up: simvx.core.input.actions.BindingSpec

None

down: simvx.core.input.actions.BindingSpec

None

left: simvx.core.input.actions.BindingSpec

None

right: simvx.core.input.actions.BindingSpec

None

deadzone: float

0.2

WASD: ClassVar[simvx.core.input.actions.Axis2D]

None

ARROWS: ClassVar[simvx.core.input.actions.Axis2D]

None

__post_init__() → None[source]
property parts: tuple[simvx.core.input.actions.Part, simvx.core.input.actions.Part, simvx.core.input.actions.Part, simvx.core.input.actions.Part][source]

This source’s components as declared, (up, down, left, right): bindings, or names.

property bindings: tuple[simvx.core.input.events.InputBinding, simvx.core.input.events.InputBinding, simvx.core.input.events.InputBinding, simvx.core.input.events.InputBinding][source]

This source’s components, (up, down, left, right), once the map has resolved every name.

__mul__(factor: float) → simvx.core.input.actions.Axis2D[source]

Every direction multiplied into the action’s unit: Touch.PAN * 0.01 reads the screen’s width as 1.

__rmul__

None

per_second(rate: float) → simvx.core.input.actions.Axis2D[source]

Every direction as motion: the held value times rate each second.

classmethod for_stick(stick: simvx.core.input.controls.DeviceStick, *, deadzone: float = 0.2) → simvx.core.input.actions.Axis2D[source]

One whole stick as a vector: the halves of its two axes, on the stick’s unit.

simvx.core.input.actions.to_source(spec: simvx.core.input.actions.SourceSpec) → simvx.core.input.actions.Source[source]

Normalise one entry of an action’s binding list into a source.

A bare axis becomes the :class:Axis1D over its two halves and a bare stick the :class:Axis2D over its two axes, so everything below this point deals in three source types rather than in every spelling of them.

simvx.core.input.actions.kind_for(name: str, sources: list[simvx.core.input.actions.Source]) → simvx.core.input.actions.ActionKind[source]

The type an action has, given its sources, refusing a list that has none.

The widest source decides, and a narrower one has to have an unambiguous reading inside it: a button in a float action pushes it positive, while a button or a scalar in a 2D action names no direction at all and is refused here rather than resolving to something the game did not mean.

class simvx.core.input.actions.Action[source]

One entry of the input map: what it is called, what type it has, what drives it.

An action and a device alias are both entries. sources each produce a whole value on their own and may name other entries; once the map has expanded them, bindings is the same thing flattened to device bindings, which is what a prompt and the event resolver read. An entry keyed by a control rather than a name is a game’s redefinition of an engine entry, Controller.A, and has exactly one source.

name: str | simvx.core.input.controls.DeviceControl

None

kind: simvx.core.input.actions.ActionKind

None

sources: list[simvx.core.input.actions.Source]

‘field(…)’

bindings: list[simvx.core.input.events.InputBinding]

‘field(…)’

refresh() → None[source]

Recompute the kind and the flattened bindings from the sources.

A name in the sources contributes nothing here: the map expands it, and the expanded entry is the one the resolver reads.

property names: tuple[str, ...][source]

Every entry this one names, in its sources and in its composites’ parts, in order.