simvx.core.input.devices

The devices under the player, and the pads among them as objects.

Input.devices is a mapping of the engine’s id to every device the machine has, each an :class:InputDevice, and Input.controllers is the pads among them, each a :class:ControllerDevice. A ControllerDevice answers the questions a game asks of one pad: what it is called, which family of controller it belongs to (so a prompt can say Cross rather than A), whether its buttons carry the engine’s standard meanings, what is held on it, where its sticks are, and whether it can be made to vibrate.

A device is a live view, not a snapshot. It holds the store and its own id and reads through on every query, so the object a game stored at connection never goes stale and a per-frame poll allocates nothing beyond the

class:

~simvx.core.math.types.Vec2 a stick has always returned. A snapshot would have to be rebuilt every frame by everyone who kept one, and the cost of getting that wrong is a game reading last frame’s stick forever.

A pad that disconnects answers, it does not raise. It leaves Input.controllers the moment the poll that lost it closes, so a game enumerating pads never sees one mid-teardown; but a ControllerDevice a game is still holding keeps working and reports a pad with nothing held: :attr:connected is False, every button is up, every axis is zero, and :meth:rumble does nothing and returns False. The release edges the disconnect produced are readable for the rest of that frame, which is what lets a game stop what the player was doing rather than run on the last reading.

The device record is process-wide, held once in :data:store and read through every tree’s Input: the held keys and mouse buttons, the fingers on the glass, the pads and the input method. A key is down on the machine and a controller is plugged into the machine, not into a scene tree, so an editor and the game it is running agree about both. Action state, contexts and focus are the other half and stay per tree, resolved from the tree’s own delivered record of what actually reached it (see

class:

~simvx.core.input.state._Input), which is why a key the editor never forwarded presses nothing in the game while Input.is_key_pressed there still says the machine is holding it.

A test isolates devices in time, not in space. There is no second store to run a test against, in the same way that there is no second machine; what a test has instead is :func:reset, which puts the pads and the input method back as they were at process start so an injected pad cannot leak into the next test. Input._reset() calls it, so a test that already resets input needs nothing further, and a test that only wants the hardware facts cleared calls it directly. Its sibling is

func:

simvx.core.input.capabilities.reset, which does the same for the capability flags and is deliberately separate: a capability is declared once by a backend and a pad arrives and leaves many times.

Module Contents

Classes

AxisReport

What an axis’s hardware reports: where it is, how far it moved, or both.

ButtonInfo

One of a device’s buttons, as a rebind screen lists it.

AxisInfo

One of a device’s axes, as a rebind screen lists it.

InputDevice

One device: the keyboard, the mouse, a touchscreen, a pen, a tablet.

ControllerDevice

One connected pad. Read it through Input.controllers[device_id].

Functions

family_for

Which controller family a pad belongs to, from the name its driver reports.

wrapped_delta

The change from before to now on a scale that wraps at 1.0, the short way round.

check_axes

Validate what a backend announced for a device’s axes.

reset

Put the pads and the input method back as they were at process start.

hold_frame

Run a guest tree’s frame without spending the devices’ frame.

Data

API

simvx.core.input.devices.__all__

[‘AxisInfo’, ‘AxisReport’, ‘ButtonInfo’, ‘ControllerDevice’, ‘InputDevice’, ‘family_for’, ‘hold_fram…

simvx.core.input.devices.STICK_DEADZONE

0.15

simvx.core.input.devices.family_for(name: str, guid: str = '', *, recognised: bool = True) → simvx.core.input.enums.ControllerFamily[source]

Which controller family a pad belongs to, from the name its driver reports.

The family decides what a prompt draws, and nothing else: the engine’s controls are positional either way, so Controller.A is the bottom face button on every pad and only its printed label moves.

A pad the engine cannot lay out as a standard controller (recognised False) is

Attr:

~simvx.core.input.enums.ControllerFamily.JOYSTICK whatever it is called, because naming its buttons after a controller’s would be a guess. One that is laid out but whose name reveals no family is

Attr:

~simvx.core.input.enums.ControllerFamily.GENERIC, which prompts with the engine’s own names.

class simvx.core.input.devices.AxisReport[source]

Bases: enum.StrEnum

What an axis’s hardware reports: where it is, how far it moved, or both.

A ring reports an absolute angle on some drivers and deltas on others, so this is declared per axis by whatever connects the device.

Initialization

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

ABSOLUTE

‘absolute’

RELATIVE

‘relative’

BOTH

‘both’

__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.devices.ButtonInfo[source]

One of a device’s buttons, as a rebind screen lists it.

Attributes: index: The device’s own index. control: What the layout in force calls it: a name, or the index form. label: What to print for it.

index: int

None

control: simvx.core.input.controls.DeviceButton

None

label: str

None

class simvx.core.input.devices.AxisInfo[source]

One of a device’s axes, as a rebind screen lists it.

Attributes: index: The device’s own index. control: What the layout in force calls it. label: What to print for it. report: What the hardware reports for it. wraps: Whether its position wraps at 1.0, as a ring’s does, which is what makes a delta across the wrap the short way round.

index: int

None

control: simvx.core.input.controls.DeviceAxis

None

label: str

None

report: simvx.core.input.devices.AxisReport

None

wraps: bool

None

simvx.core.input.devices.wrapped_delta(now: float, before: float) → float[source]

The change from before to now on a scale that wraps at 1.0, the short way round.

simvx.core.input.devices.check_axes(reports: collections.abc.Sequence[simvx.core.input.devices.AxisReport | str], wraps: collections.abc.Collection[int]) → tuple[tuple[simvx.core.input.devices.AxisReport, ...], frozenset[int]][source]

Validate what a backend announced for a device’s axes.

class simvx.core.input.devices.InputDevice(store: simvx.core.input.devices._DeviceStore, device_id: int)[source]

One device: the keyboard, the mouse, a touchscreen, a pen, a tablet.

A live view, like :class:Controller: it holds the store and its own id and reads through on every query, so a device a game kept keeps answering, and one that has gone reports nothing held.

Read them through Input.devices. A rebind screen lists :attr:buttons and :attr:axes and prints :meth:label.

Initialization

__slots__

(‘_id’, ‘_store’)

__repr__() → str[source]
property id: int[source]

The engine’s id for the device, which every event it produces carries as device_id.

property type: simvx.core.input.controls.DeviceType[source]

What type of device this is.

property name: str[source]

What the driver calls it.

property guid: str[source]

The model’s stable id, the same for every unit of it, or "" where the backend has none.

property connected: bool[source]

Whether the device is still there.

property buttons: tuple[simvx.core.input.devices.ButtonInfo, ...][source]

Every button the device reported, with the control it is and its label.

As many as the backend announced, or as many as the layout names where it announced none.

property axes: tuple[simvx.core.input.devices.AxisInfo, ...][source]

Every axis the device reported, with the control it is, its label and what it reports.

label(control: simvx.core.input.controls.DeviceControl) → str[source]

What this device prints on control, for a rebind screen or a prompt.

"" for a control this device’s layout does not have.

pressed(button: simvx.core.input.controls.DeviceButton) → bool[source]

Whether button is held on this device right now.

axis(axis: simvx.core.input.controls.DeviceAxis) → float | None[source]

Where axis is, or None for an axis that reports only motion, or that nothing names.

axis_delta(axis: simvx.core.input.controls.DeviceAxis) → float[source]

How far axis moved this frame, in the same units as its position.

class simvx.core.input.devices.ControllerDevice(store: simvx.core.input.devices._DeviceStore, device_id: int)[source]

Bases: simvx.core.input.devices.InputDevice

One connected pad. Read it through Input.controllers[device_id].

A game normally binds actions and never touches this; it is here for what a binding cannot express: which pad a local-multiplayer player is holding, what to draw as a prompt, and rumble. It is a device like any other in Input.devices, with what only a pad has besides.

Initialization

__slots__

()

__repr__() → str[source]
property source: simvx.core.input.enums.ControllerSource[source]

Where the pad comes from: real hardware, a test, or the on-screen pad.

property family: simvx.core.input.enums.ControllerFamily[source]

Which controller family it belongs to, which is what a prompt draws.

property recognised: bool[source]

Whether a controller database recognised this device and laid it out in the standard positions.

Recognised, Controller.A is its bottom face button. Not, it has only the driver’s numbered controls, Controller.button(i) and Controller.axis(i), until the player maps names onto them: InputMap.rebind(Controller.A, Controller.button(3).on(device)) makes Controller.A that device’s button 3, for actions, handlers and these queries alike, and is saved with the player’s rebinds. Until then a named query on it raises, naming what to do, and an action bound to a name reads at rest on it.

property connected: bool[source]

Whether the pad is still there.

False for a pad that has gone: every query below then answers as an untouched pad rather than raising, so a game holding this object across the disconnect keeps running.

pressed(button: simvx.core.input.controls.DeviceButton) → bool[source]

Whether button is held on this controller right now.

Raises ValueError for a named control (Controller.A) on a controller that is not :attr:recognised and that the player has not mapped it on.

axis_delta(axis: simvx.core.input.controls.DeviceAxis) → float[source]

How far axis moved this frame; a named axis on an unrecognised controller raises, as :meth:pressed.

just_pressed(button: simvx.core.input.controls.DeviceButton) → bool[source]

Did button go down on this pad during this frame?

Inside a physics tick, during the frame that tick opens: the frame’s edges belong to its first tick and to no other, the same rule every other edge query follows, so a press fires once whatever the display and the tick rate are doing.

just_released(button: simvx.core.input.controls.DeviceButton) → bool[source]

Did button come up on this pad during this frame?

True for every button a pad was holding when it was unplugged, for the frame the disconnect happened on. Tick-scoped inside a tick, as

Meth:

just_pressed.

axis(axis: simvx.core.input.controls.DeviceAxis) → float[source]

One axis in the engine’s convention, with no deadzone applied.

Sticks run -1.0 to 1.0 with +y down the screen; triggers run 0.0 released to 1.0 fully pulled. Raw because a deadzone is a gameplay decision: :meth:stick applies the radial one a stick wants, and an action binding applies its own. A named axis on a controller that is not :attr:recognised raises, as :meth:pressed does.

stick(stick: simvx.core.input.controls.DeviceStick = Controller.LEFT_STICK, *, deadzone: float = STICK_DEADZONE) → simvx.core.math.types.Vec2[source]

One stick as a vector, zeroed inside a radial deadzone.

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 unchanged when it is not.

rumble(strong: float = 1.0, weak: float = 0.0, duration: float = 0.2) → bool[source]

Vibrate the pad, returning whether anything happened.

Args: strong: The heavy low-frequency motor, 0.0 to 1.0. weak: The light high-frequency motor, 0.0 to 1.0. duration: Seconds to run for.

Returns: False when this pad cannot vibrate, when the backend supplies no rumble at all (Input.capabilities.rumble is then False), or when the pad has disconnected. A game may call it unconditionally.

label(control: simvx.core.input.controls.DeviceControl, *, positive: bool | None = None) → str[source]

What this pad prints on a button or an axis.

"A" on an Xbox pad, "Cross" on a PlayStation one and "B" on a Nintendo one, all for Controller.A: the controls are positional and only the printing moves. positive names one half of a stick axis, which is what a binding splits it into. A control the pad’s layout does not name prints as its number.

property id: int
property type: simvx.core.input.controls.DeviceType
property name: str
property guid: str
property buttons: tuple[simvx.core.input.devices.ButtonInfo, ...]
property axes: tuple[simvx.core.input.devices.AxisInfo, ...]
simvx.core.input.devices.store

‘_DeviceStore(…)’

simvx.core.input.devices.reset() → None[source]

Put the pads and the input method back as they were at process start.

simvx.core.input.devices.hold_frame() → contextlib.AbstractContextManager[None][source]

Run a guest tree’s frame without spending the devices’ frame.

See :meth:_DeviceStore.hold_frame, which this stands in front of so a caller needs the module and not the object.