Skip to content

Commit 2b8388f

Browse files
committed
Add controller events.
1 parent a0602f8 commit 2b8388f

File tree

3 files changed

+168
-11
lines changed

3 files changed

+168
-11
lines changed

examples/eventget.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# https://creativecommons.org/publicdomain/zero/1.0/
66
"""An demonstration of event handling using the tcod.event module.
77
"""
8-
from typing import List
8+
from typing import List, Set
99

1010
import tcod
1111
import tcod.sdl.joystick
@@ -19,7 +19,9 @@ def main() -> None:
1919

2020
event_log: List[str] = []
2121
motion_desc = ""
22-
joysticks = tcod.sdl.joystick.get_joysticks()
22+
tcod.sdl.joystick.init()
23+
controllers: Set[tcod.sdl.joystick.GameController] = set()
24+
joysticks: Set[tcod.sdl.joystick.Joystick] = set()
2325

2426
with tcod.context.new(width=WIDTH, height=HEIGHT) as context:
2527
console = context.new_console()
@@ -42,9 +44,19 @@ def main() -> None:
4244
raise SystemExit()
4345
if isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWRESIZED":
4446
console = context.new_console()
47+
if isinstance(event, tcod.event.ControllerDevice):
48+
if event.type == "CONTROLLERDEVICEADDED":
49+
controllers.add(event.controller)
50+
elif event.type == "CONTROLLERDEVICEREMOVED":
51+
controllers.remove(event.controller)
52+
if isinstance(event, tcod.event.JoystickDevice):
53+
if event.type == "JOYDEVICEADDED":
54+
joysticks.add(event.joystick)
55+
elif event.type == "JOYDEVICEREMOVED":
56+
joysticks.remove(event.joystick)
4557
if isinstance(event, tcod.event.MouseMotion):
4658
motion_desc = str(event)
47-
else:
59+
else: # Log all events other than MouseMotion.
4860
event_log.append(str(event))
4961

5062

tcod/event.py

Lines changed: 139 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
from typing_extensions import Final, Literal
9191

9292
import tcod.event_constants
93+
import tcod.sdl.joystick
9394
from tcod.event_constants import * # noqa: F4
9495
from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT
9596
from tcod.loader import ffi, lib
@@ -791,6 +792,12 @@ def __init__(self, type: str, which: int):
791792
self.which = which
792793
"""The ID of the joystick this event is for."""
793794

795+
@property
796+
def joystick(self) -> tcod.sdl.joystick.Joystick:
797+
if self.type == "JOYDEVICEADDED":
798+
return tcod.sdl.joystick.Joystick._open(self.which)
799+
return tcod.sdl.joystick.Joystick._from_instance_id(self.which)
800+
794801
def __repr__(self) -> str:
795802
return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which})"
796803

@@ -952,17 +959,16 @@ class JoystickDevice(JoystickEvent):
952959
953960
Example::
954961
955-
joysticks: dict[int, tcod.sdl.joystick.Joystick] = {}
962+
joysticks: set[tcod.sdl.joystick.Joystick] = {}
956963
for event in tcod.event.get():
957964
match event:
958-
case tcod.event.JoystickDevice(type="JOYDEVICEADDED", which=device_id):
959-
new_joystick = tcod.sdl.joystick.Joystick(device_id)
960-
joysticks[new_joystick.id] = new_joystick
961-
case tcod.event.JoystickDevice(type="JOYDEVICEREMOVED", which=which):
962-
del joysticks[which]
965+
case tcod.event.JoystickDevice(type="JOYDEVICEADDED", joystick=new_joystick):
966+
joysticks.add(new_joystick)
967+
case tcod.event.JoystickDevice(type="JOYDEVICEREMOVED", joystick=joystick):
968+
joysticks.remove(joystick)
963969
"""
964970

965-
type = Final[Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]] # type: ignore[assignment,misc]
971+
type: Final[Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]] # type: ignore[misc]
966972

967973
which: int
968974
"""When type="JOYDEVICEADDED" this is the device ID.
@@ -975,6 +981,126 @@ def from_sdl_event(cls, sdl_event: Any) -> JoystickDevice:
975981
return cls(type, sdl_event.jdevice.which)
976982

977983

984+
class ControllerEvent(Event):
985+
"""Base class for controller events.
986+
987+
.. versionadded:: Unreleased
988+
"""
989+
990+
def __init__(self, type: str, which: int):
991+
super().__init__(type)
992+
self.which = which
993+
"""The ID of the joystick this event is for."""
994+
995+
@property
996+
def controller(self) -> tcod.sdl.joystick.GameController:
997+
"""The :any:`GameController: for this event."""
998+
if self.type == "CONTROLLERDEVICEADDED":
999+
return tcod.sdl.joystick.GameController._open(self.which)
1000+
return tcod.sdl.joystick.GameController._from_instance_id(self.which)
1001+
1002+
def __repr__(self) -> str:
1003+
return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which})"
1004+
1005+
def __str__(self) -> str:
1006+
prefix = super().__str__().strip("<>")
1007+
return f"<{prefix}, which={self.which}>"
1008+
1009+
1010+
class ControllerAxis(ControllerEvent):
1011+
"""When a controller axis is moved.
1012+
1013+
.. versionadded:: Unreleased
1014+
"""
1015+
1016+
type: Final[Literal["CONTROLLERAXISMOTION"]] # type: ignore[misc]
1017+
1018+
def __init__(self, type: str, which: int, axis: tcod.sdl.joystick.ControllerAxis, value: int):
1019+
super().__init__(type, which)
1020+
self.axis = axis
1021+
"""Which axis is being moved. One of :any:`ControllerAxis`."""
1022+
self.value = value
1023+
"""The new value of this events axis.
1024+
1025+
This will be -32768 to 32767 for all axes except for triggers which are 0 to 32767 instead."""
1026+
1027+
@classmethod
1028+
def from_sdl_event(cls, sdl_event: Any) -> ControllerAxis:
1029+
return cls(
1030+
"CONTROLLERAXISMOTION",
1031+
sdl_event.caxis.which,
1032+
tcod.sdl.joystick.ControllerAxis(sdl_event.caxis.axis),
1033+
sdl_event.caxis.value,
1034+
)
1035+
1036+
def __repr__(self) -> str:
1037+
return (
1038+
f"tcod.event.{self.__class__.__name__}"
1039+
f"(type={self.type!r}, which={self.which}, axis={self.axis}, value={self.value})"
1040+
)
1041+
1042+
def __str__(self) -> str:
1043+
prefix = super().__str__().strip("<>")
1044+
return f"<{prefix}, axis={self.axis}, value={self.value}>"
1045+
1046+
1047+
class ControllerButton(ControllerEvent):
1048+
"""When a controller button is pressed or released.
1049+
1050+
.. versionadded:: Unreleased
1051+
"""
1052+
1053+
type: Final[Literal["CONTROLLERBUTTONDOWN", "CONTROLLERBUTTONUP"]] # type: ignore[misc]
1054+
1055+
def __init__(self, type: str, which: int, button: tcod.sdl.joystick.ControllerButton, pressed: bool):
1056+
super().__init__(type, which)
1057+
self.button = button
1058+
"""The button for this event. One of :any:`ControllerButton`."""
1059+
self.pressed = pressed
1060+
"""True if the button was pressed, False if it was released."""
1061+
1062+
@classmethod
1063+
def from_sdl_event(cls, sdl_event: Any) -> ControllerButton:
1064+
type = {
1065+
lib.SDL_CONTROLLERBUTTONDOWN: "CONTROLLERBUTTONDOWN",
1066+
lib.SDL_CONTROLLERBUTTONUP: "CONTROLLERBUTTONUP",
1067+
}[sdl_event.type]
1068+
return cls(
1069+
type,
1070+
sdl_event.cbutton.which,
1071+
tcod.sdl.joystick.ControllerButton(sdl_event.cbutton.button),
1072+
sdl_event.cbutton.state == lib.SDL_PRESSED,
1073+
)
1074+
1075+
def __repr__(self) -> str:
1076+
return (
1077+
f"tcod.event.{self.__class__.__name__}"
1078+
f"(type={self.type!r}, which={self.which}, button={self.button}, pressed={self.pressed})"
1079+
)
1080+
1081+
def __str__(self) -> str:
1082+
prefix = super().__str__().strip("<>")
1083+
return f"<{prefix}, button={self.button}, pressed={self.pressed}>"
1084+
1085+
1086+
class ControllerDevice(ControllerEvent):
1087+
"""When a controller is added, removed, or remapped.
1088+
1089+
.. versionadded:: Unreleased
1090+
"""
1091+
1092+
type: Final[Literal["CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMOVED", "CONTROLLERDEVICEREMAPPED"]] # type: ignore[misc]
1093+
1094+
@classmethod
1095+
def from_sdl_event(cls, sdl_event: Any) -> ControllerDevice:
1096+
type = {
1097+
lib.SDL_CONTROLLERDEVICEADDED: "CONTROLLERDEVICEADDED",
1098+
lib.SDL_CONTROLLERDEVICEREMOVED: "CONTROLLERDEVICEREMOVED",
1099+
lib.SDL_CONTROLLERDEVICEREMAPPED: "CONTROLLERDEVICEREMAPPED",
1100+
}[sdl_event.type]
1101+
return cls(type, sdl_event.cdevice.which)
1102+
1103+
9781104
class Undefined(Event):
9791105
"""This class is a place holder for SDL events without their own tcod.event
9801106
class.
@@ -1012,6 +1138,12 @@ def __str__(self) -> str:
10121138
lib.SDL_JOYBUTTONUP: JoystickButton,
10131139
lib.SDL_JOYDEVICEADDED: JoystickDevice,
10141140
lib.SDL_JOYDEVICEREMOVED: JoystickDevice,
1141+
lib.SDL_CONTROLLERAXISMOTION: ControllerAxis,
1142+
lib.SDL_CONTROLLERBUTTONDOWN: ControllerButton,
1143+
lib.SDL_CONTROLLERBUTTONUP: ControllerButton,
1144+
lib.SDL_CONTROLLERDEVICEADDED: ControllerDevice,
1145+
lib.SDL_CONTROLLERDEVICEREMOVED: ControllerDevice,
1146+
lib.SDL_CONTROLLERDEVICEREMAPPED: ControllerDevice,
10151147
}
10161148

10171149

tcod/sdl/joystick.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ def _open(cls, device_index: int) -> Joystick:
148148
p = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose))
149149
return cls(p)
150150

151+
@classmethod
152+
def _from_instance_id(cls, instance_id: int) -> Joystick:
153+
return cls(_check_p(ffi.gc(lib.SDL_JoystickFromInstanceID(instance_id), lib.SDL_JoystickClose)))
154+
151155
def __eq__(self, other: object) -> bool:
152156
if isinstance(other, GameController):
153157
return self == other.joystick.id
@@ -199,6 +203,10 @@ def __init__(self, sdl_controller_p: Any):
199203
def _open(cls, joystick_index: int) -> GameController:
200204
return cls(_check_p(ffi.gc(lib.SDL_GameControllerOpen(joystick_index), lib.SDL_GameControllerClose)))
201205

206+
@classmethod
207+
def _from_instance_id(cls, instance_id: int) -> GameController:
208+
return cls(_check_p(ffi.gc(lib.SDL_GameControllerFromInstanceID(instance_id), lib.SDL_GameControllerClose)))
209+
202210
def get_button(self, button: ControllerButton) -> bool:
203211
"""Return True if `button` is currently held."""
204212
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, button))
@@ -347,9 +355,14 @@ def _touchpad(self) -> bool:
347355
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_TOUCHPAD))
348356

349357

358+
def init() -> None:
359+
"""Initialize SDL's joystick and game controller subsystems."""
360+
tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK | tcod.sdl.sys.Subsystem.GAMECONTROLLER)
361+
362+
350363
def _get_number() -> int:
351364
"""Return the number of attached joysticks."""
352-
tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK)
365+
init()
353366
return _check(lib.SDL_NumJoysticks())
354367

355368

0 commit comments

Comments
 (0)