Source code for simvx.core.input.prompts

"""What to print on screen for an input, on the device the player is holding.

A pad's controls are positional: ``Controller.A`` is the bottom face button on
every controller. What is *printed* on that button is not. It says A
on an Xbox pad, Cross on a PlayStation pad and B on a Nintendo one, because
Nintendo's face buttons are laid out the other way round, so a game that draws
"Press A" draws the wrong letter for two of the three. That swap is the whole
reason this module exists; the rest is spelling.

``Input.prompt("jump")`` is the ordinary entry point: it picks the binding that
matches whatever the player is using and labels it. The two functions below are
for a game drawing its own prompt for a button it names directly.
"""

from __future__ import annotations

from .controls import Controller, DeviceAxis, DeviceButton, DeviceType, key_to_name
from .enums import ControllerFamily

__all__ = ["axis_label", "button_label", "key_binding_label", "key_label", "pointer_button_label"]

#: What each family prints on a face, shoulder or system button. Only the
#: entries that differ from :data:`_GENERIC_BUTTONS` are listed; a family falls
#: back to it for the rest.
#:
#: Nintendo's swap is positional, not a rename: the engine's ``A`` is the
#: bottom button, which a Switch pad labels B.
_FAMILY_BUTTONS: dict[ControllerFamily, dict[DeviceButton, str]] = {
    ControllerFamily.XBOX: {
        Controller.LEFT_BUMPER: "LB",
        Controller.RIGHT_BUMPER: "RB",
        Controller.BACK: "View",
        Controller.START: "Menu",
        Controller.GUIDE: "Xbox",
        Controller.LEFT_THUMB: "LS",
        Controller.RIGHT_THUMB: "RS",
    },
    ControllerFamily.PLAYSTATION: {
        Controller.A: "Cross",
        Controller.B: "Circle",
        Controller.X: "Square",
        Controller.Y: "Triangle",
        Controller.LEFT_BUMPER: "L1",
        Controller.RIGHT_BUMPER: "R1",
        Controller.BACK: "Share",
        Controller.START: "Options",
        Controller.GUIDE: "PS",
        Controller.LEFT_THUMB: "L3",
        Controller.RIGHT_THUMB: "R3",
    },
    ControllerFamily.NINTENDO: {
        Controller.A: "B",
        Controller.B: "A",
        Controller.X: "Y",
        Controller.Y: "X",
        Controller.LEFT_BUMPER: "L",
        Controller.RIGHT_BUMPER: "R",
        Controller.BACK: "Minus",
        Controller.START: "Plus",
        Controller.GUIDE: "Home",
        Controller.LEFT_THUMB: "LS",
        Controller.RIGHT_THUMB: "RS",
    },
}

_GENERIC_BUTTONS: dict[DeviceButton, str] = {
    Controller.A: "A",
    Controller.B: "B",
    Controller.X: "X",
    Controller.Y: "Y",
    Controller.LEFT_BUMPER: "L1",
    Controller.RIGHT_BUMPER: "R1",
    Controller.BACK: "Select",
    Controller.START: "Start",
    Controller.GUIDE: "Guide",
    Controller.LEFT_THUMB: "L3",
    Controller.RIGHT_THUMB: "R3",
    Controller.DPAD_UP: "D-Pad Up",
    Controller.DPAD_DOWN: "D-Pad Down",
    Controller.DPAD_LEFT: "D-Pad Left",
    Controller.DPAD_RIGHT: "D-Pad Right",
}

_FAMILY_TRIGGERS: dict[ControllerFamily, tuple[str, str]] = {
    ControllerFamily.XBOX: ("LT", "RT"),
    ControllerFamily.PLAYSTATION: ("L2", "R2"),
    ControllerFamily.NINTENDO: ("ZL", "ZR"),
}
_GENERIC_TRIGGERS = ("LT", "RT")

#: Which stick an axis belongs to, and what the two ends of it are called.
_STICK_AXES: dict[DeviceAxis, tuple[str, str, str]] = {
    Controller.LEFT_X: ("Left Stick", "Left", "Right"),
    Controller.LEFT_Y: ("Left Stick", "Up", "Down"),
    Controller.RIGHT_X: ("Right Stick", "Left", "Right"),
    Controller.RIGHT_Y: ("Right Stick", "Up", "Down"),
}

#: Keys whose engine name is not what a player would want to read.
_KEY_LABELS: dict[str, str] = {
    "escape": "Esc",
    "pageup": "Page Up",
    "pagedown": "Page Down",
    "space": "Space",
    "enter": "Enter",
    "tab": "Tab",
    "backspace": "Backspace",
    "delete": "Delete",
    "insert": "Insert",
    "ctrl": "Ctrl",
    "shift": "Shift",
    "alt": "Alt",
    "super": "Super",
}


[docs] def button_label(button: DeviceButton, family: ControllerFamily = ControllerFamily.GENERIC) -> str: """What *family* prints on *button*. A :attr:`~.enums.ControllerFamily.JOYSTICK` has no face buttons the engine can name, so it falls back to the generic spelling rather than inventing one. """ return _FAMILY_BUTTONS.get(family, {}).get(button) or _GENERIC_BUTTONS.get(button, str(button))
[docs] def axis_label( axis: DeviceAxis, family: ControllerFamily = ControllerFamily.GENERIC, *, positive: bool | None = None ) -> str: """What *family* prints on *axis*, optionally for one half of its travel. ``positive`` names a direction: a binding splits a stick axis in two, and "Left Stick Right" is what that half of it is called. Left None the whole axis is named. A trigger has one direction and ignores it. """ if positive is None and axis.half: positive = axis.half > 0 axis = axis.whole if axis in (Controller.LEFT_TRIGGER, Controller.RIGHT_TRIGGER): left, right = _FAMILY_TRIGGERS.get(family, _GENERIC_TRIGGERS) return left if axis is Controller.LEFT_TRIGGER else right if axis not in _STICK_AXES: return str(axis) name, negative_end, positive_end = _STICK_AXES[axis] if positive is None: return name return f"{name} {positive_end if positive else negative_end}"
[docs] def key_binding_label(key_combo: str) -> str: """A combo the engine spells ``"shift+tab"``, as a player would read it.""" return "+".join( _KEY_LABELS.get(part, part.upper() if len(part) == 1 else part.capitalize()) for part in key_combo.split("+") )
[docs] def pointer_button_label(button: DeviceButton) -> str: """What to print for a pointer or tablet button, through the layout of the device that has it. A role prints as the mouse's member of it, which is what a prompt for a player at a desk wants; any other button prints as its own device type does, and as the device plugged in does where there is one, so a rebind screen reads what the player's own hardware calls the button. """ from .devices import store device_type = DeviceType.MOUSE if button.device is DeviceType.POINTER else button.device rec = next((r for r in store.device_records.values() if r.type is device_type and r.connected), None) layout = store.layout_of(rec) if rec is not None else store.type_layout(device_type) index = layout.button_index(button) if index is None: return str(button) return layout.button_label(index)
[docs] def key_label(key: DeviceButton) -> str: """What to print for one key, with no modifiers.""" return key_binding_label(key_to_name(key))