Skip to content

Commit a0602f8

Browse files
committed
Add GameController class.
1 parent 6e71a35 commit a0602f8

File tree

1 file changed

+280
-9
lines changed

1 file changed

+280
-9
lines changed

tcod/sdl/joystick.py

Lines changed: 280 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from __future__ import annotations
66

77
import enum
8-
from typing import Dict, List, Optional, Tuple
8+
from typing import Any, Dict, List, Optional, Tuple, Union
99

1010
from typing_extensions import Final, Literal
1111

@@ -26,6 +26,72 @@
2626
}
2727

2828

29+
class ControllerAxis(enum.IntEnum):
30+
"""The standard axes for a game controller."""
31+
32+
INVALID = lib.SDL_CONTROLLER_AXIS_INVALID or -1
33+
LEFTX = lib.SDL_CONTROLLER_AXIS_LEFTX or 0
34+
""""""
35+
LEFTY = lib.SDL_CONTROLLER_AXIS_LEFTY or 1
36+
""""""
37+
RIGHTX = lib.SDL_CONTROLLER_AXIS_RIGHTX or 2
38+
""""""
39+
RIGHTY = lib.SDL_CONTROLLER_AXIS_RIGHTY or 3
40+
""""""
41+
TRIGGERLEFT = lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT or 4
42+
""""""
43+
TRIGGERRIGHT = lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT or 5
44+
""""""
45+
46+
47+
class ControllerButton(enum.IntEnum):
48+
"""The standard buttons for a game controller."""
49+
50+
INVALID = lib.SDL_CONTROLLER_BUTTON_INVALID or -1
51+
A = lib.SDL_CONTROLLER_BUTTON_A or 0
52+
""""""
53+
B = lib.SDL_CONTROLLER_BUTTON_B or 1
54+
""""""
55+
X = lib.SDL_CONTROLLER_BUTTON_X or 2
56+
""""""
57+
Y = lib.SDL_CONTROLLER_BUTTON_Y or 3
58+
""""""
59+
BACK = lib.SDL_CONTROLLER_BUTTON_BACK or 4
60+
""""""
61+
GUIDE = lib.SDL_CONTROLLER_BUTTON_GUIDE or 5
62+
""""""
63+
START = lib.SDL_CONTROLLER_BUTTON_START or 6
64+
""""""
65+
LEFTSTICK = lib.SDL_CONTROLLER_BUTTON_LEFTSTICK or 7
66+
""""""
67+
RIGHTSTICK = lib.SDL_CONTROLLER_BUTTON_RIGHTSTICK or 8
68+
""""""
69+
LEFTSHOULDER = lib.SDL_CONTROLLER_BUTTON_LEFTSHOULDER or 9
70+
""""""
71+
RIGHTSHOULDER = lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER or 10
72+
""""""
73+
DPAD_UP = lib.SDL_CONTROLLER_BUTTON_DPAD_UP or 11
74+
""""""
75+
DPAD_DOWN = lib.SDL_CONTROLLER_BUTTON_DPAD_DOWN or 12
76+
""""""
77+
DPAD_LEFT = lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT or 13
78+
""""""
79+
DPAD_RIGHT = lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT or 14
80+
""""""
81+
MISC1 = 15
82+
""""""
83+
PADDLE1 = 16
84+
""""""
85+
PADDLE2 = 17
86+
""""""
87+
PADDLE3 = 18
88+
""""""
89+
PADDLE4 = 19
90+
""""""
91+
TOUCHPAD = 20
92+
""""""
93+
94+
2995
class Power(enum.IntEnum):
3096
"""The possible power states of a controller.
3197
@@ -50,15 +116,14 @@ class Power(enum.IntEnum):
50116

51117

52118
class Joystick:
53-
"""An SDL joystick.
119+
"""A low-level SDL joystick.
54120
55121
.. seealso::
56122
https://wiki.libsdl.org/CategoryJoystick
57123
"""
58124

59-
def __init__(self, device_index: int):
60-
tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK)
61-
self.sdl_joystick_p: Final = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose))
125+
def __init__(self, sdl_joystick_p: Any):
126+
self.sdl_joystick_p: Final = sdl_joystick_p
62127
"""The CFFI pointer to an SDL_Joystick struct."""
63128
self.axes: Final[int] = _check(lib.SDL_JoystickNumAxes(self.sdl_joystick_p))
64129
"""The total number of axes."""
@@ -74,6 +139,24 @@ def __init__(self, device_index: int):
74139
"""The GUID of this joystick."""
75140
self.id: Final[int] = _check(lib.SDL_JoystickInstanceID(self.sdl_joystick_p))
76141
"""The instance ID of this joystick. This is not the same as the device ID."""
142+
self._keep_alive: Any = None
143+
"""The owner of this objects memory if this object does not own itself."""
144+
145+
@classmethod
146+
def _open(cls, device_index: int) -> Joystick:
147+
tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK)
148+
p = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose))
149+
return cls(p)
150+
151+
def __eq__(self, other: object) -> bool:
152+
if isinstance(other, GameController):
153+
return self == other.joystick.id
154+
if isinstance(other, Joystick):
155+
return self.id == other.id
156+
return NotImplemented
157+
158+
def __hash__(self) -> int:
159+
return hash(self.id)
77160

78161
def _get_guid(self) -> str:
79162
guid_str = ffi.new("char[33]")
@@ -95,30 +178,218 @@ def get_ball(self, ball: int) -> Tuple[int, int]:
95178
return int(xy[0]), int(xy[1])
96179

97180
def get_button(self, button: int) -> bool:
98-
"""Return True if `button` is pressed."""
181+
"""Return True if `button` is currently held."""
99182
return bool(lib.SDL_JoystickGetButton(self.sdl_joystick_p, button))
100183

101184
def get_hat(self, hat: int) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]:
102185
"""Return the direction of `hat` as (x, y). With (-1, -1) being in the upper-left."""
103186
return _HAT_DIRECTIONS[lib.SDL_JoystickGetHat(self.sdl_joystick_p, hat)]
104187

105188

106-
def get_number() -> int:
189+
class GameController:
190+
"""A standard interface for an Xbox 360 style game controller."""
191+
192+
def __init__(self, sdl_controller_p: Any):
193+
self.sdl_controller_p: Final = sdl_controller_p
194+
self.joystick: Final = Joystick(lib.SDL_GameControllerGetJoystick(self.sdl_controller_p))
195+
"""The :any:`Joystick` associated with this controller."""
196+
self.joystick._keep_alive = self.sdl_controller_p # This objects real owner needs to be kept alive.
197+
198+
@classmethod
199+
def _open(cls, joystick_index: int) -> GameController:
200+
return cls(_check_p(ffi.gc(lib.SDL_GameControllerOpen(joystick_index), lib.SDL_GameControllerClose)))
201+
202+
def get_button(self, button: ControllerButton) -> bool:
203+
"""Return True if `button` is currently held."""
204+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, button))
205+
206+
def get_axis(self, axis: ControllerAxis) -> int:
207+
"""Return the state of the given `axis`.
208+
209+
The state is usually a value from -32768 to 32767, with positive values towards the lower-right direction.
210+
Triggers have the range of 0 to 32767 instead.
211+
"""
212+
return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, axis))
213+
214+
def __eq__(self, other: object) -> bool:
215+
if isinstance(other, GameController):
216+
return self.joystick.id == other.joystick.id
217+
if isinstance(other, Joystick):
218+
return self.joystick.id == other.id
219+
return NotImplemented
220+
221+
def __hash__(self) -> int:
222+
return hash(self.joystick.id)
223+
224+
# These could exist as convenience functions, but the get_X functions are probably better.
225+
@property
226+
def _left_x(self) -> int:
227+
"Return the position of this axis. (-32768 to 32767)"
228+
return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_LEFTX))
229+
230+
@property
231+
def _left_y(self) -> int:
232+
"Return the position of this axis. (-32768 to 32767)"
233+
return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_LEFTY))
234+
235+
@property
236+
def _right_x(self) -> int:
237+
"Return the position of this axis. (-32768 to 32767)"
238+
return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_RIGHTX))
239+
240+
@property
241+
def _right_y(self) -> int:
242+
"Return the position of this axis. (-32768 to 32767)"
243+
return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_RIGHTY))
244+
245+
@property
246+
def _trigger_left(self) -> int:
247+
"Return the position of this trigger. (0 to 32767)"
248+
return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT))
249+
250+
@property
251+
def _trigger_right(self) -> int:
252+
"Return the position of this trigger. (0 to 32767)"
253+
return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT))
254+
255+
@property
256+
def _a(self) -> bool:
257+
"""Return True if this button is held."""
258+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_A))
259+
260+
@property
261+
def _b(self) -> bool:
262+
"""Return True if this button is held."""
263+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_B))
264+
265+
@property
266+
def _x(self) -> bool:
267+
"""Return True if this button is held."""
268+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_X))
269+
270+
@property
271+
def _y(self) -> bool:
272+
"""Return True if this button is held."""
273+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_Y))
274+
275+
@property
276+
def _back(self) -> bool:
277+
"""Return True if this button is held."""
278+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_BACK))
279+
280+
@property
281+
def _guide(self) -> bool:
282+
"""Return True if this button is held."""
283+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_GUIDE))
284+
285+
@property
286+
def _start(self) -> bool:
287+
"""Return True if this button is held."""
288+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_START))
289+
290+
@property
291+
def _left_stick(self) -> bool:
292+
"""Return True if this button is held."""
293+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_LEFTSTICK))
294+
295+
@property
296+
def _right_stick(self) -> bool:
297+
"""Return True if this button is held."""
298+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_RIGHTSTICK))
299+
300+
@property
301+
def _left_shoulder(self) -> bool:
302+
"""Return True if this button is held."""
303+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_LEFTSHOULDER))
304+
305+
@property
306+
def _right_shoulder(self) -> bool:
307+
"""Return True if this button is held."""
308+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER))
309+
310+
@property
311+
def _dpad(self) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]:
312+
return (
313+
lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
314+
- lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT),
315+
lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_DOWN)
316+
- lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_UP),
317+
)
318+
319+
@property
320+
def _misc1(self) -> bool:
321+
"""Return True if this button is held."""
322+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_MISC1))
323+
324+
@property
325+
def _paddle1(self) -> bool:
326+
"""Return True if this button is held."""
327+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE1))
328+
329+
@property
330+
def _paddle2(self) -> bool:
331+
"""Return True if this button is held."""
332+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE2))
333+
334+
@property
335+
def _paddle3(self) -> bool:
336+
"""Return True if this button is held."""
337+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE3))
338+
339+
@property
340+
def _paddle4(self) -> bool:
341+
"""Return True if this button is held."""
342+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE4))
343+
344+
@property
345+
def _touchpad(self) -> bool:
346+
"""Return True if this button is held."""
347+
return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_TOUCHPAD))
348+
349+
350+
def _get_number() -> int:
107351
"""Return the number of attached joysticks."""
108352
tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK)
109353
return _check(lib.SDL_NumJoysticks())
110354

111355

112356
def get_joysticks() -> List[Joystick]:
113357
"""Return a list of all connected joystick devices."""
114-
return [Joystick(i) for i in range(get_number())]
358+
return [Joystick._open(i) for i in range(_get_number())]
359+
360+
361+
def get_controllers() -> List[GameController]:
362+
"""Return a list of all connected game controllers.
363+
364+
This ignores joysticks without a game controller mapping.
365+
"""
366+
return [GameController._open(i) for i in range(_get_number()) if lib.SDL_IsGameController(i)]
115367

116368

117-
def event_state(new_state: Optional[bool] = None) -> bool:
369+
def get_all() -> List[Union[Joystick, GameController]]:
370+
"""Return a list of all connected joystick or controller devices.
371+
372+
If the joystick has a controller mapping then it is returned as a :any:`GameController`.
373+
Otherwise it is returned as a :any:`Joystick`.
374+
"""
375+
return [GameController._open(i) if lib.SDL_IsGameController(i) else Joystick._open(i) for i in range(_get_number())]
376+
377+
378+
def joystick_event_state(new_state: Optional[bool] = None) -> bool:
118379
"""Check or set joystick event polling.
119380
120381
.. seealso::
121382
https://wiki.libsdl.org/SDL_JoystickEventState
122383
"""
123384
_OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_IGNORE, True: lib.SDL_ENABLE}
124385
return bool(_check(lib.SDL_JoystickEventState(_OPTIONS[new_state])))
386+
387+
388+
def controller_event_state(new_state: Optional[bool] = None) -> bool:
389+
"""Check or set game controller event polling.
390+
391+
.. seealso::
392+
https://wiki.libsdl.org/SDL_GameControllerEventState
393+
"""
394+
_OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_IGNORE, True: lib.SDL_ENABLE}
395+
return bool(_check(lib.SDL_GameControllerEventState(_OPTIONS[new_state])))

0 commit comments

Comments
 (0)