simvx.core.input.events¶
Input bindings, and the typed event family every input flows through.
One class per device family, all of them subclasses of :class:InputEvent. A
handler’s filter decides which one it receives, so a typed signature needs no
narrowing and match event: reads as ordinary Python::
match event:
case KeyEvent(key=Key.ESCAPE, pressed=True):
self.pause()
case PointerEvent(phase=PointerPhase.DOWN, position=pos):
self.aim_at(pos)
Every class is a @dataclass(slots=True, kw_only=True): no per-instance
__dict__, a free repr, and keyword-only fields so a subclass can declare
a required one after the base’s defaulted ones. kw_only also leaves
__match_args__ empty, which is why the patterns above are keyword patterns.
The base carries what every input has: when it happened, the modifier state at that moment, and whether something has claimed it.
Module Contents¶
Classes¶
One input an action reads: a control pressed while other controls are held. |
|
What every input carries, whatever device produced it. |
|
A key going down or coming up. |
|
Text the player typed, after the layout and the IME have had their say. |
|
A mouse, a finger or a pen doing something. |
|
One live pointer as it stands this frame, as |
|
A wheel notch or a two-finger scroll. |
|
A button with no position going down or coming up: a pad’s button, a tablet’s express key. |
|
An axis with no position moving: a pad’s stick or trigger, a tablet’s ring or strip. |
|
An action edge, resolved from whatever device binding produced it. |
|
A ray through the pointer hitting a node, delivered to |
|
A controller arriving or leaving. |
Functions¶
How one key of a combo is written: a modifier group by its name, any other key by its own. |
|
Spell a key and what it holds as a combo ( |
|
The modifier groups event says were held, in combo order: |
|
The key a combo part names: a modifier group ( |
|
Parse a combo such as |
|
The key event a written combo names: |
Data¶
API¶
- simvx.core.input.events.MODIFIER_NAMES¶
(‘ctrl’, ‘shift’, ‘alt’, ‘meta’)
- class simvx.core.input.events.InputBinding[source]¶
One input an action reads: a control pressed while other controls are held.
controlis any control: a key (Key.SPACE), a pointer, tablet or pad button (Mouse.RIGHT,Tablet.button(0),Controller.A), a group (Pointer.SECONDARY,Key.CTRL), or one half of an axis (Controller.LEFT_X.negative). A control pinned to a unit with.on(device)hears that device alone.heldis what must be down for the press to count, which is how a shortcut, a chord or a modifier is written, all alike::Key.CTRL + Key.S # Ctrl+S, either Control key Key.RIGHT_CONTROL + Key.S # the right Control key only Key.META + Key.S # Cmd+S on a Mac, Super+S elsewhere Key.G + Key.X # a two-key chord Controller.LEFT_BUMPER + Controller.A
+builds the binding; the last control is the one pressed.exact. Whether modifiers the binding does not hold may also be down. Left unset it follows what the binding holds: a binding holding a modifier is exact, because Ctrl+S must not also fire on Ctrl+Shift+S, and a binding holding none is not, because a jump on Space must still fire while the player holds Shift to run.
exact=Falsemakes Ctrl+S fire under any extra modifier;exact=Truemakes a bare S fire only with no modifier down. A held control that is not a modifier (Key.Gin G+X) is required either way, and the pressed control’s own modifier never counts against it.deadzonegoverns an axis: how far past rest its half must travel to count.Units.
scalemultiplies what the binding reads into the action’s unit, andrate, when set, makes a held value motion: the value timesrateper second, over the frame or tick it is read in. So sources with different units share one action, each written in the action’s unit::"zoom": [Touch.PINCH, # the unit: a fraction of scale Mouse.WHEEL * 0.1, # a notch is a tenth Controller.RIGHT_TRIGGER.per_second(1.0)] # held fully, doubles a secondThe deadzone is measured before either, on what the device reports.
- control: simvx.core.input.controls.DeviceButton | simvx.core.input.controls.DeviceAxis | None¶
None
- deadzone: float¶
0.2
- held: tuple[simvx.core.input.controls.DeviceButton, ...]¶
()
- exact: bool | None¶
None
- scale: float¶
1.0
- rate: float | None¶
None
- holding(*controls: simvx.core.input.controls.DeviceButton) simvx.core.input.events.InputBinding[source]¶
This binding with controls held as well, before the ones it already holds.
- __add__(other: simvx.core.input.controls.DeviceControl) simvx.core.input.events.InputBinding[source]¶
Extend a chord:
Key.CTRL + Key.SHIFT + Key.Sholds both and presses S.
- __mul__(factor: float) simvx.core.input.events.InputBinding[source]¶
This binding with its value multiplied into the action’s unit:
(Key.CTRL + Pointer.PRIMARY) * 2.
- __rmul__¶
None
- per_second(rate: float) simvx.core.input.events.InputBinding[source]¶
This binding as motion: its held value times rate each second, over the step it is read in.
- property modifiers: frozenset[simvx.core.input.controls.DeviceButton][source]¶
The modifiers this binding holds, as groups:
{Key.CTRL}for either Control key or one side.
- property is_exact: bool[source]¶
Whether this binding refuses modifiers it does not hold:
exact, or its default when unset.
- property key_combo: str | None[source]¶
The combo spelling of a key binding (
"ctrl+shift+s"), or None if it is not keys alone.
- matches_key(event: simvx.core.input.events.KeyEvent) bool[source]¶
Whether the key event is this binding, by the rule a binding is read by.
The key, every modifier the binding holds, and where it is exact (:attr:
is_exact) no other modifier, the pressed key’s own excepted. So the engine’s Tab, which is exact, is not Shift+Tab, and a plain Enter still accepts while Shift is held. The event says which modifiers were down, not which side, so a side reads as its modifier, and a held control that is not a modifier is not something a key event can say, which no event matches.
- simvx.core.input.events.key_part(key: simvx.core.input.controls.DeviceButton) str[source]¶
How one key of a combo is written: a modifier group by its name, any other key by its own.
- simvx.core.input.events.combo_for_key(key: simvx.core.input.controls.DeviceButton, *, held: tuple[simvx.core.input.controls.DeviceButton, ...] = ()) str[source]¶
Spell a key and what it holds as a combo (
"ctrl+s"): the one place the spelling is written.For the interfaces spelled in text: a project file, a rebind screen, a menu’s shortcut column. Nothing on the input path compares combos.
- simvx.core.input.events.modifiers_held(event: InputEvent) tuple[simvx.core.input.controls.DeviceButton, ...][source]¶
The modifier groups event says were held, in combo order:
(Key.CTRL, Key.SHIFT).
- simvx.core.input.events.key_named(name: str) simvx.core.input.controls.DeviceButton | None[source]¶
The key a combo part names: a modifier group (
"ctrl"), a key name, or a key’s constant name.
- simvx.core.input.events.key_combo_to_binding(spec: str) simvx.core.input.events.InputBinding | None[source]¶
Parse a combo such as
"ctrl+s"or"g+x"into a binding; None when spec is not one.The inverse of :attr:
InputBinding.key_combo: every part but the last is held, the last is pressed, and"ctrl","shift","alt"and"meta"are either side. A bare key name is not a combo and returns None, leaving it to the caller’s own key lookup.
- simvx.core.input.events.key_event_for_combo(spec: str, *, pressed: bool = True) KeyEvent[source]¶
The key event a written combo names:
"escape","ctrl+s".For the drivers that are themselves written in text (a test harness, a scripted demo). Raises
ValueErrornaming the spec when no key answers to it, or when the combo holds something a key event cannot carry.
- class simvx.core.input.events.InputEvent[source]¶
What every input carries, whatever device produced it.
Attributes: time: When the input happened, on :func:
time.monotonic’s clock, which is the clock the frame loop measures with. A backend that has a platform event time converts it onto that clock; one that does not lets :class:~simvx.core.input.router.InputRouterstamp the moment it received the event. :attr:Input.capabilities.event_timestamps <simvx.core.input.capabilities.InputCapabilities.event_timestamps>says whether the source can supply one at all. Not the scene clock: that one stops while paused and stretches under slow motion, so a double click would stop resolving on a pause menu. source_id: Which input source delivered this, or 0 for one nobody attributed. A source is a window, not a screen: two windows onto one tree have two pointer positions, two hover answers and two capture tables, and this is what tells them apart. Each :class:~simvx.core.input.router.InputRoutertakes an id at construction and stamps it on everything it publishes, so an event carries its source rather than the caller having to pass one alongside it. ctrl, shift, alt, meta: Modifier state at the moment of the event. A backend that does not report them leaves them unset and the router fills them from held keys.- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.KeyEvent[source]¶
Bases:
simvx.core.input.events.InputEventA key going down or coming up.
Attributes: key: The physical key position,
Key.W: the named key where the engine’s table names the platform’s code,Key.button(code)where it does not. A binding and a saved rebind file both name this, so WASD is the same four positions on QWERTY and on AZERTY. label: What that key prints under the player’s layout, for a prompt or a rebind screen. The router fills it from the engine’s US table when the backend cannot say. pressed: True on press, False on release. echo: True for an OS auto-repeat of a key already held. cancelled: True on a release that did not happen: the control that owned the press was hidden, disabled, paused or raised while the key was down, and is told the press is over without it counting as a keystroke, as a pointer’sCANCELphase tells a drag. device_id: Which physical keyboard, or 0 for the aggregate device.- key: simvx.core.input.controls.DeviceButton¶
None
- pressed: bool¶
None
- label: str = <Multiline-String>¶
- echo: bool¶
False
- device_id: int¶
0
- cancelled: bool¶
False
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.TextEvent[source]¶
Bases:
simvx.core.input.events.InputEventText the player typed, after the layout and the IME have had their say.
Attributes: text: The characters produced. composing: True while an IME composition is still in flight, so a text field can underline the preedit rather than commit it.
- text: str¶
None
- composing: bool¶
False
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.PointerEvent[source]¶
Bases:
simvx.core.input.events.InputEventA mouse, a finger or a pen doing something.
Every finger is a pointer of its own and so is a pen, each delivered through the same door the mouse is. One of them at a time is primary: the one that stands in for the mouse, so a control that takes one pointer and, while
Input.emulate_mouseis on, the mouse state and the left-button bindings follow it.A button is one control. :attr:
buttonis what the device’s layout calls the button pressed,Pen.BARREL_1, or its index form where the layout has no name. A role is a group, asked within::if event.phase is PointerPhase.DOWN and event.button in Pointer.PRIMARY: self.press()Contact is not the phase. :attr:
contactis the primary role’s bit: a finger on the glass, a pen tip on the tablet, the left mouse button held. A pen hovering over the tablet producesMOVEevents with no contact and a pressure of 0.0, and a pen pressing a barrel button while it hovers is aDOWNwith no contact, which is how a hover press is bound at all.Attributes: pointer_id: 0 is the mouse. Every other pointer’s id is opaque, allocated by the router per device and backend id: a finger gets a fresh one each time it touches down and a pen keeps one while it is in range. :attr:
pointer_typesays what kind it is. pointer_type: Which kind of pointer this is. is_primary: True for the pointer that stands in for the mouse: the mouse itself, the first finger down while no other pointer holds primacy, and a pen likewise. Primacy moves on a press only. phase: What the pointer did. position: Where it is, in screen pixels, sub-pixel. delta: How far it moved since the previous event for this pointer. button: The button this event is about, as the layout in force names it (Mouse.LEFT,Pen.BARREL_1,Touch.CONTACT), or its index form (Pen.button(5)) where the layout names none; None for a pure move. An event built by hand may name either, or a role; publishing it resolves the button through the device’s layout. buttons: Bitmask of the buttons this pointer holds once the event has happened, bitindexeach, in the device’s own numbering. For the mouse, the buttons the machine holds. contact_mask: The bits of :attr:buttonsthat are contact: the primary role’s, from the device’s layout. click_count: 1 for a single click, 2 for a double, and so on, on the press and on the release that closes it; 0 on a pure move and wherever nothing has resolved it. dragging: True once the pointer has travelled past its type’s drag threshold since the press, and on every event of that press after. A release still carrying False is a click rather than a drag. pressure: 0.0 whenever the pointer is not in contact. In contact, what the device measured, or 1.0 on hardware that cannot measure it;Input.capabilitiessays which (PenAxis.PRESSURE in pen_axesfor a pen,touch_pressurefor a finger). tilt: The barrel’s tilt from upright as(x, y), radians in[-pi/2, pi/2]; zero for a device without one. twist: The barrel’s rotation about its own axis, radians in[0, 2pi)clockwise; zero where not reported. distance: How far a hovering pen is from the surface, 0 to 1, 0 at the surface; zero where not reported. tangential_pressure: An airbrush wheel or barrel squeeze, -1 to 1 with 0 neutral; zero where not reported. tool: Which end of the pointer is working: the tip, or a pen’s eraser. tool_id: Which physical tool, for a per-tool preset; 0 where not reported. contact_size: The width and height of the contact in screen pixels, a finger or a palm on the glass;(0, 0)where not reported. locked: True while the pointer is locked andpositionis stale. viewport: The spacepositionis in, or None for the root. device_id: The engine’s id for the device, the key inInput.devices, allocated per type and backend id; 0 on an event built by hand, which publishing resolves to the type’s aggregate device.- phase: simvx.core.input.enums.PointerPhase¶
None
- pointer_id: int¶
0
- pointer_type: simvx.core.input.enums.PointerType¶
None
- is_primary: bool¶
True
- position: tuple[float, float]¶
(0.0, 0.0)
- delta: tuple[float, float]¶
(0.0, 0.0)
- button: simvx.core.input.controls.DeviceButton | None¶
None
- buttons: int¶
0
- contact_mask: int¶
1
- click_count: int¶
0
- dragging: bool¶
False
- pressure: float¶
0.0
- tilt: tuple[float, float]¶
(0.0, 0.0)
- twist: float¶
0.0
- distance: float¶
0.0
- tangential_pressure: float¶
0.0
- tool: simvx.core.input.enums.PointerTool¶
None
- tool_id: int¶
0
- contact_size: tuple[float, float]¶
(0.0, 0.0)
- locked: bool¶
False
- viewport: simvx.core.viewport.SubViewport | None¶
None
- device_id: int¶
0
- property contact: bool[source]¶
Whether the pointer is touching: a finger down, a pen tip down, the mouse’s primary held.
Read off :attr:
buttonsthrough :attr:contact_mask. An event built by hand and published is completed by the router first, so a press that named only its button holds it here too.
- property altitude: float[source]¶
The angle between the barrel and the surface, radians: pi/2 upright, 0 flat.
- property azimuth: float[source]¶
The direction the barrel leans, radians in
[0, 2pi); 0 at zero tilt.
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.PointerState[source]¶
One live pointer as it stands this frame, as
Input.pointersholds it.The polled companion of :class:
PointerEvent, with the same field names, so a game can poll every finger and a pen the way it polls a key. A finger is live from its press until the frame after it lifts; a pen from the moment it is seen until the frame after it leaves the digitiser’s range; the mouse once it has done anything at all.Attributes: pointer_id: 0 is the mouse; every other id is opaque, as :attr:
PointerEvent.pointer_id. pointer_type: Which kind of pointer this is. position: Where it is, in screen pixels. contact: Whether it is touching, as :attr:PointerEvent.contact. is_primary: Whether it stands in for the mouse. buttons: The buttons it holds, bitindexeach in the device’s own numbering. pressure, tilt, twist, distance, tangential_pressure, tool, tool_id, contact_size: As on :class:PointerEvent, from its latest event. just_pressed: Contact began this frame. just_released: Contact ended this frame, lifted or cancelled. cancelled: The contact that ended was taken away by the system rather than lifted, so it completed nothing: a gesture reading a tap off a release checks this first. device_id: The engine’s id for the device, as :attr:PointerEvent.device_id.- pointer_id: int¶
None
- pointer_type: simvx.core.input.enums.PointerType¶
None
- position: tuple[float, float]¶
None
- contact: bool¶
False
- is_primary: bool¶
False
- buttons: int¶
0
- pressure: float¶
0.0
- tilt: tuple[float, float]¶
(0.0, 0.0)
- twist: float¶
0.0
- distance: float¶
0.0
- tangential_pressure: float¶
0.0
- tool: simvx.core.input.enums.PointerTool¶
None
- tool_id: int¶
0
- contact_size: tuple[float, float]¶
(0.0, 0.0)
- just_pressed: bool¶
False
- just_released: bool¶
False
- cancelled: bool¶
False
- device_id: int¶
0
- class simvx.core.input.events.ScrollEvent[source]¶
Bases:
simvx.core.input.events.InputEventA wheel notch or a two-finger scroll.
Attributes: pointer_id: The pointer the scroll came from. position: Where the pointer was, in screen pixels. scroll: The offset, positive
yscrolling up. A wheel notch is one unit; a trackpad reports fractions of one. viewport: The spacepositionis in, or None for the root.- scroll: tuple[float, float]¶
None
- pointer_id: int¶
0
- position: tuple[float, float]¶
(0.0, 0.0)
- viewport: simvx.core.viewport.SubViewport | None¶
None
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.ButtonEvent[source]¶
Bases:
simvx.core.input.events.InputEventA button with no position going down or coming up: a pad’s button, a tablet’s express key.
A pointer’s buttons arrive on its :class:
PointerEvent, because a press there has a place. This is the same model for a device that has no place to press: one control in :attr:button.Attributes: button: What the layout in force calls the button,
Controller.A, or its index form,Tablet.button(3). pressed: True on press, False on release. device_id: Which device, the engine’s id for it.- button: simvx.core.input.controls.DeviceButton¶
None
- pressed: bool¶
None
- device_id: int¶
0
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.AxisEvent[source]¶
Bases:
simvx.core.input.events.InputEventAn axis with no position moving: a pad’s stick or trigger, a tablet’s ring or strip.
Both halves of what an axis can say, whichever its hardware reports: where it is and how far it moved. A ring that reports only motion has no position, and :attr:
valuesays so with None rather than a number that would mean nothing.Attributes: axis: What the layout in force calls the axis,
Controller.LEFT_XorTablet.RING, or its index form,Tablet.axis(0). value: Where it is now, or None for an axis that reports only motion. delta: How far it moved in this report, the short way round a ring. device_id: Which device, the engine’s id for it.- axis: simvx.core.input.controls.DeviceAxis¶
None
- value: float | None¶
None
- delta: float¶
0.0
- device_id: int¶
0
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.ActionEvent[source]¶
Bases:
simvx.core.input.events.InputEventAn action edge, resolved from whatever device binding produced it.
What an action handler,
@on_input("jump"), receives. It asked about an action, so it is given the action: which one, which way it went, what it reads, whose it is, and the device event underneath it.Attributes: action: The action’s name. pressed: True as the action opens, False as it closes. value: What the action reads at this edge, in the type its bindings gave it:
boolfor a button action,floatfor one carrying anAxis1Dor a bare axis,Vec2for one carrying anAxis2Dor a stick. player: Whose action this is. 0 is the default player, which owns every device nobody else was paired to, so a single-player game sees 0 throughout. source: The device event the binding resolved from, or None. context: The input context that declared the action, or None.- action: str¶
None
- pressed: bool¶
None
- value: bool | float | simvx.core.math.types.Vec2¶
False
- player: int¶
0
- source: simvx.core.input.events.InputEvent | None¶
None
- context: simvx.core.input.contexts.InputContext | None¶
None
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.PickEvent[source]¶
Bases:
simvx.core.input.events.InputEventA ray through the pointer hitting a node, delivered to
on_picked.Attributes: source: The pointer event the ray was cast for, or None. viewport: The space the pointer was in, or None for the root. button: Which button was involved, as :attr:
PointerEvent.button. pressed: True on press, False on release. origin: World-space ray origin. direction: World-space ray direction, normalised. distance: Distance along the ray to the hit. node: The node that was hit, or None. point: The world-space hit point, or None where it is not computed. normal: The surface normal there, or None where it is not computed.- origin: simvx.core.math.types.Vec3¶
None
- direction: simvx.core.math.types.Vec3¶
None
- source: simvx.core.input.events.InputEvent | None¶
None
- viewport: simvx.core.viewport.SubViewport | None¶
None
- button: simvx.core.input.controls.DeviceButton | None¶
None
- pressed: bool¶
True
- distance: float¶
0.0
- node: simvx.core.node.Node | None¶
None
- point: simvx.core.math.types.Vec3 | None¶
None
- normal: simvx.core.math.types.Vec3 | None¶
None
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- class simvx.core.input.events.ControllerConnectionEvent[source]¶
Bases:
simvx.core.input.events.InputEventA controller arriving or leaving.
Attributes: device_id: Which pad, the key it has in
Input.controllers. connected: True when it arrived, False when it left.- device_id: int¶
None
- connected: bool¶
None
- time: float¶
‘field(…)’
- source_id: int¶
0
- ctrl: bool¶
False
- shift: bool¶
False
- alt: bool¶
False
- meta: bool¶
False
- simvx.core.input.events.GuiInputEvent¶
None