Source code for simvx.core.input.events

"""Input binding and event types."""

from dataclasses import dataclass

from .enums import JoyAxis, JoyButton, Key, MouseButton, key_to_name, name_to_keys

MODIFIER_NAMES = ("ctrl", "shift", "alt")


[docs] @dataclass class InputBinding: """A single input binding: maps to a key, mouse button, or gamepad input. ``ctrl`` / ``shift`` / ``alt`` are REQUIRED modifiers, not exclusive ones: a binding that sets ``shift=True`` only matches while Shift is held, while a binding that leaves them all False matches whatever else is held. That keeps every unmodified binding behaving as it always has (a jump bound to Space still fires while the player holds Shift to run). Key routing that has to tell one combo from another -- the UI navigation actions, which must distinguish Tab from Shift+Tab -- compares :attr:`key_combo` against the combo the router built for the event, so there the match is exact. """ key: Key | None = None mouse_button: MouseButton | None = None joy_button: JoyButton | None = None joy_axis: JoyAxis | None = None joy_axis_positive: bool = True # For splitting an axis into two actions deadzone: float = 0.2 ctrl: bool = False shift: bool = False alt: bool = False
[docs] @property def key_combo(self) -> str | None: """The combo spelling of a key binding (``"shift+tab"``), or None if not a key. Modifier order is ctrl, shift, alt, matching the combo string the UI router builds from the live modifier state. """ if self.key is None: return None parts = [name for name, held in (("ctrl", self.ctrl), ("shift", self.shift), ("alt", self.alt)) if held] parts.append(key_to_name(self.key)) return "+".join(parts)
[docs] def key_combo_to_binding(spec: str) -> InputBinding | None: """Parse a combo such as ``"shift+tab"`` into a binding; None when *spec* is not one. The inverse of :attr:`InputBinding.key_combo`, and the one place that spelling is understood: the input map, the project file and anything else that accepts a written binding all resolve combos through here, so a combo that an ``InputMap`` accepts is also one a ``simvx.toml`` can carry. A bare key name is not a combo (there is nothing before the ``+``) and returns None, leaving it to the caller's own key lookup. """ parts = spec.lower().split("+") mods = set(parts[:-1]) if not mods or not mods.issubset(MODIFIER_NAMES): return None keys = name_to_keys(parts[-1]) if keys: key = keys[0] else: try: key = Key[parts[-1].upper()] except KeyError: return None return InputBinding(key=key, ctrl="ctrl" in mods, shift="shift" in mods, alt="alt" in mods)
[docs] class InputEventKey: """Keyboard input event with rich metadata.""" __slots__ = ("key", "pressed", "echo", "shift", "ctrl", "alt", "meta", "handled") def __init__( self, key: Key, pressed: bool, echo: bool = False, shift: bool = False, ctrl: bool = False, alt: bool = False, meta: bool = False, ): self.key = key self.pressed = pressed self.echo = echo self.shift = shift self.ctrl = ctrl self.alt = alt self.meta = meta self.handled = False
[docs] def __repr__(self) -> str: mods = [] if self.shift: mods.append("Shift") if self.ctrl: mods.append("Ctrl") if self.alt: mods.append("Alt") if self.meta: mods.append("Meta") prefix = "+".join(mods) + "+" if mods else "" action = "pressed" if self.pressed else "released" return f"InputEventKey({prefix}{self.key.name} {action})"
[docs] class InputEventMouse: """Mouse button input event.""" __slots__ = ("button", "pressed", "position", "shift", "ctrl", "alt", "meta", "handled") def __init__( self, button: MouseButton, pressed: bool, position: tuple[float, float] = (0.0, 0.0), shift: bool = False, ctrl: bool = False, alt: bool = False, meta: bool = False, ): self.button = button self.pressed = pressed self.position = position self.shift = shift self.ctrl = ctrl self.alt = alt self.meta = meta self.handled = False
[docs] def __repr__(self) -> str: action = "pressed" if self.pressed else "released" return f"InputEventMouse({self.button.name} {action} at {self.position})"