"""Input device definitions and state callbacks."""
from __future__ import annotations
from ctypes import POINTER, Structure, c_char_p, c_int16, c_uint
from dataclasses import dataclass
from enum import CONFORM, IntEnum, IntFlag
from typing import NewType, overload
from libretro.api._utils import MemoDict, deepcopy_array
from libretro.ctypes import CIntArg, TypedFunctionPointer, TypedPointer
Port = NewType("Port", int)
"""
A controller :term:`port` index.
Intended for type safety when working with controller ports.
"""
RETRO_DEVICE_NONE = 0
RETRO_DEVICE_JOYPAD = 1
RETRO_DEVICE_MOUSE = 2
RETRO_DEVICE_KEYBOARD = 3
RETRO_DEVICE_LIGHTGUN = 4
RETRO_DEVICE_ANALOG = 5
RETRO_DEVICE_POINTER = 6
retro_input_poll_t = TypedFunctionPointer[None, []]
"""
Poll input devices for the current frame.
Registered by the :term:`frontend` and called by the :term:`core`
at least once per :c:func:`retro_run` to refresh cached input state
before any calls to :c:type:`retro_input_state_t`.
Corresponds to :c:type:`retro_input_poll_t` in ``libretro.h``.
.. seealso::
:attr:`.InputDriver.poll`
The :class:`.InputDriver` method that implements this callback.
:attr:`.Core.set_input_poll`
The method that exposes this callback to a :term:`core`.
"""
retro_input_state_t = TypedFunctionPointer[
c_int16, [CIntArg[c_uint], CIntArg[c_uint], CIntArg[c_uint], CIntArg[c_uint]]
]
"""
Query the state of a specific input on a controller.
Registered by the :term:`frontend` and called by the :term:`core`
to read the most recently polled input.
:param port: Index of the controller :term:`port` to query.
:param device: One of the :class:`InputDevice` constants identifying the abstract device type.
The value is masked with ``RETRO_DEVICE_MASK``.
:param index: Sub-index whose meaning depends on ``device``
(e.g. an analog stick index).
:param id: Identifier of the specific input to read,
such as one of the ``RETRO_DEVICE_ID_*`` constants.
:return: The current input value;
semantics depend on ``device`` and ``id``,
and ``0`` is returned for unsupported combinations.
Corresponds to :c:type:`retro_input_state_t` in ``libretro.h``.
.. seealso::
:attr:`.InputDriver.state`
The suggested entry point for this callback in libretro.py.
:attr:`.Core.set_input_state`
The method that exposes this callback to a :term:`core`.
"""
RETRO_DEVICE_TYPE_SHIFT = 8
RETRO_DEVICE_MASK = (1 << RETRO_DEVICE_TYPE_SHIFT) - 1
[docs]
def RETRO_DEVICE_SUBCLASS(base: int, id: int) -> int:
"""Generate an ID for a libretro input device subclass."""
return ((id + 1) << RETRO_DEVICE_TYPE_SHIFT) | base
[docs]
@dataclass(init=False, slots=True)
class retro_controller_description(Structure):
"""
Description of a specific kind of controller emulated by a :term:`core`.
Corresponds to :c:type:`retro_controller_description` in ``libretro.h``.
"""
desc: bytes | None
"""Human-readable name of the controller type."""
id: int
"""Device type identifier, used with :c:func:`retro_set_controller_port_device`."""
_fields_ = (
("desc", c_char_p),
("id", c_uint),
)
[docs]
def __deepcopy__(self, _):
"""
Create a deep copy of this object, including all strings.
Intended for use with :func:`copy.deepcopy`.
"""
return retro_controller_description(self.desc, self.id)
[docs]
@dataclass(init=False, slots=True)
class retro_controller_info(Structure):
r"""
List of controller types usable by a controller :term:`port`.
Can be indexed like a :class:`.Sequence`
to access individual :class:`.retro_controller_description`\s.
Corresponds to :c:type:`retro_controller_info` in ``libretro.h``.
"""
types: TypedPointer[retro_controller_description] | None
"""Array of controller types supported by this port."""
num_types: int
"""Number of entries in :attr:`types`."""
_fields_ = (
("types", POINTER(retro_controller_description)),
("num_types", c_uint),
)
[docs]
def __deepcopy__(self, memo: MemoDict):
"""
Return a deep copy of this object, including all strings and arrays.
Intended for use with :func:`copy.deepcopy`.
"""
return retro_controller_info(
types=(deepcopy_array(self.types, self.num_types, memo) if self.types else None),
num_types=self.num_types,
)
@overload
def __getitem__(self, index: int) -> retro_controller_description: ...
@overload
def __getitem__(
self, index: slice[retro_controller_description]
) -> list[retro_controller_description]: ...
[docs]
def __getitem__(self, index: int | slice[retro_controller_description]):
"""
Return the :class:`.retro_controller_description` at the given index or slice.
:param index: An integer index or slice object.
:return: A single :class:`.retro_controller_description` if ``index`` is an :class:`int`,
or a :class:`list` of them if it's a :class:`slice`.
:raises ValueError: If there are no controller types available.
:raises IndexError: If ``index`` is out of range.
:raises TypeError: If ``index`` isn't an :class:`int` or :class:`slice`.
"""
if not self.types:
raise ValueError("No controller types available")
match index:
case int(i) if 0 <= i < self.num_types:
return self.types[i]
case int(i):
raise IndexError(f"Expected 0 <= index < {len(self)}, got {i}")
case slice() as s:
return self.types[s]
case _:
raise TypeError(f"Expected an int or slice index, got {type(index).__name__}")
[docs]
def __len__(self):
"""
Return the number of device types.
:return: :attr:`num_types`.
"""
return self.num_types
__all__ = [
"InputDeviceFlag",
"InputDevice",
"RETRO_DEVICE_SUBCLASS",
"retro_input_poll_t",
"retro_input_state_t",
"retro_input_descriptor",
"retro_controller_description",
"retro_controller_info",
"InputDeviceState",
"Port",
]