pyglet 2.1.12__py3-none-any.whl → 3.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. pyglet/__init__.py +67 -61
  2. pyglet/__init__.pyi +15 -8
  3. pyglet/app/__init__.py +22 -13
  4. pyglet/app/async_app.py +212 -0
  5. pyglet/app/base.py +2 -1
  6. pyglet/app/{xlib.py → linux.py} +3 -3
  7. pyglet/config/__init__.py +101 -0
  8. pyglet/config/gl/__init__.py +30 -0
  9. pyglet/config/gl/egl.py +120 -0
  10. pyglet/config/gl/macos.py +262 -0
  11. pyglet/config/gl/windows.py +267 -0
  12. pyglet/config/gl/x11.py +142 -0
  13. pyglet/customtypes.py +43 -2
  14. pyglet/display/__init__.py +8 -6
  15. pyglet/display/base.py +3 -63
  16. pyglet/display/cocoa.py +12 -17
  17. pyglet/display/emscripten.py +39 -0
  18. pyglet/display/headless.py +23 -30
  19. pyglet/display/wayland.py +157 -0
  20. pyglet/display/win32.py +4 -17
  21. pyglet/display/xlib.py +19 -27
  22. pyglet/display/xlib_vidmoderestore.py +2 -2
  23. pyglet/enums.py +183 -0
  24. pyglet/event.py +0 -1
  25. pyglet/experimental/geoshader_sprite.py +15 -13
  26. pyglet/experimental/hidraw.py +6 -15
  27. pyglet/experimental/multitexture_sprite.py +31 -19
  28. pyglet/experimental/particles.py +13 -35
  29. pyglet/font/__init__.py +251 -85
  30. pyglet/font/base.py +116 -61
  31. pyglet/font/dwrite/__init__.py +349 -204
  32. pyglet/font/dwrite/dwrite_lib.py +27 -5
  33. pyglet/font/fontconfig.py +14 -6
  34. pyglet/font/freetype.py +138 -87
  35. pyglet/font/freetype_lib.py +19 -0
  36. pyglet/font/group.py +179 -0
  37. pyglet/font/harfbuzz/__init__.py +3 -3
  38. pyglet/font/pyodide_js.py +310 -0
  39. pyglet/font/quartz.py +319 -126
  40. pyglet/font/ttf.py +45 -3
  41. pyglet/font/user.py +14 -19
  42. pyglet/font/win32.py +45 -21
  43. pyglet/graphics/__init__.py +8 -787
  44. pyglet/graphics/allocation.py +115 -1
  45. pyglet/graphics/api/__init__.py +77 -0
  46. pyglet/graphics/api/base.py +299 -0
  47. pyglet/graphics/api/gl/__init__.py +58 -0
  48. pyglet/graphics/api/gl/base.py +24 -0
  49. pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
  50. pyglet/graphics/api/gl/cocoa/context.py +76 -0
  51. pyglet/graphics/api/gl/context.py +391 -0
  52. pyglet/graphics/api/gl/default_shaders.py +0 -0
  53. pyglet/graphics/api/gl/draw.py +627 -0
  54. pyglet/graphics/api/gl/egl/__init__.py +0 -0
  55. pyglet/graphics/api/gl/egl/context.py +92 -0
  56. pyglet/graphics/api/gl/enums.py +76 -0
  57. pyglet/graphics/api/gl/framebuffer.py +315 -0
  58. pyglet/graphics/api/gl/gl.py +5463 -0
  59. pyglet/graphics/api/gl/gl_info.py +188 -0
  60. pyglet/graphics/api/gl/global_opengl.py +226 -0
  61. pyglet/{gl → graphics/api/gl}/lib.py +34 -18
  62. pyglet/graphics/api/gl/shader.py +1476 -0
  63. pyglet/graphics/api/gl/shapes.py +55 -0
  64. pyglet/graphics/api/gl/sprite.py +102 -0
  65. pyglet/graphics/api/gl/state.py +219 -0
  66. pyglet/graphics/api/gl/text.py +190 -0
  67. pyglet/graphics/api/gl/texture.py +1526 -0
  68. pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
  69. pyglet/graphics/api/gl/vertexdomain.py +751 -0
  70. pyglet/graphics/api/gl/win32/__init__.py +0 -0
  71. pyglet/graphics/api/gl/win32/context.py +108 -0
  72. pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
  73. pyglet/graphics/api/gl/xlib/__init__.py +0 -0
  74. pyglet/graphics/api/gl/xlib/context.py +174 -0
  75. pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
  76. pyglet/graphics/api/gl1/__init__.py +0 -0
  77. pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
  78. pyglet/graphics/api/gl2/__init__.py +0 -0
  79. pyglet/graphics/api/gl2/buffer.py +320 -0
  80. pyglet/graphics/api/gl2/draw.py +600 -0
  81. pyglet/graphics/api/gl2/global_opengl.py +122 -0
  82. pyglet/graphics/api/gl2/shader.py +200 -0
  83. pyglet/graphics/api/gl2/shapes.py +51 -0
  84. pyglet/graphics/api/gl2/sprite.py +79 -0
  85. pyglet/graphics/api/gl2/text.py +175 -0
  86. pyglet/graphics/api/gl2/vertexdomain.py +364 -0
  87. pyglet/graphics/api/webgl/__init__.py +233 -0
  88. pyglet/graphics/api/webgl/buffer.py +302 -0
  89. pyglet/graphics/api/webgl/context.py +234 -0
  90. pyglet/graphics/api/webgl/draw.py +590 -0
  91. pyglet/graphics/api/webgl/enums.py +76 -0
  92. pyglet/graphics/api/webgl/framebuffer.py +360 -0
  93. pyglet/graphics/api/webgl/gl.py +1537 -0
  94. pyglet/graphics/api/webgl/gl_info.py +130 -0
  95. pyglet/graphics/api/webgl/shader.py +1346 -0
  96. pyglet/graphics/api/webgl/shapes.py +92 -0
  97. pyglet/graphics/api/webgl/sprite.py +102 -0
  98. pyglet/graphics/api/webgl/state.py +227 -0
  99. pyglet/graphics/api/webgl/text.py +187 -0
  100. pyglet/graphics/api/webgl/texture.py +1227 -0
  101. pyglet/graphics/api/webgl/vertexarray.py +54 -0
  102. pyglet/graphics/api/webgl/vertexdomain.py +616 -0
  103. pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
  104. pyglet/{image → graphics}/atlas.py +33 -32
  105. pyglet/graphics/base.py +10 -0
  106. pyglet/graphics/buffer.py +245 -0
  107. pyglet/graphics/draw.py +578 -0
  108. pyglet/graphics/framebuffer.py +26 -0
  109. pyglet/graphics/instance.py +178 -69
  110. pyglet/graphics/shader.py +267 -1553
  111. pyglet/graphics/state.py +83 -0
  112. pyglet/graphics/texture.py +703 -0
  113. pyglet/graphics/vertexdomain.py +695 -538
  114. pyglet/gui/ninepatch.py +10 -10
  115. pyglet/gui/widgets.py +120 -10
  116. pyglet/image/__init__.py +20 -1973
  117. pyglet/image/animation.py +12 -12
  118. pyglet/image/base.py +730 -0
  119. pyglet/image/codecs/__init__.py +9 -0
  120. pyglet/image/codecs/bmp.py +53 -30
  121. pyglet/image/codecs/dds.py +53 -31
  122. pyglet/image/codecs/gdiplus.py +38 -14
  123. pyglet/image/codecs/gdkpixbuf2.py +0 -2
  124. pyglet/image/codecs/js_image.py +99 -0
  125. pyglet/image/codecs/ktx2.py +161 -0
  126. pyglet/image/codecs/pil.py +1 -1
  127. pyglet/image/codecs/png.py +1 -1
  128. pyglet/image/codecs/wic.py +11 -2
  129. pyglet/info.py +26 -24
  130. pyglet/input/__init__.py +8 -0
  131. pyglet/input/base.py +163 -105
  132. pyglet/input/controller.py +13 -19
  133. pyglet/input/controller_db.py +39 -24
  134. pyglet/input/emscripten/__init__.py +18 -0
  135. pyglet/input/emscripten/gamepad_js.py +397 -0
  136. pyglet/input/linux/__init__.py +11 -5
  137. pyglet/input/linux/evdev.py +10 -11
  138. pyglet/input/linux/x11_xinput.py +2 -2
  139. pyglet/input/linux/x11_xinput_tablet.py +1 -1
  140. pyglet/input/macos/__init__.py +7 -2
  141. pyglet/input/macos/darwin_gc.py +559 -0
  142. pyglet/input/win32/__init__.py +1 -1
  143. pyglet/input/win32/directinput.py +34 -29
  144. pyglet/input/win32/xinput.py +11 -61
  145. pyglet/lib.py +3 -3
  146. pyglet/libs/__init__.py +1 -1
  147. pyglet/{gl → libs/darwin}/agl.py +1 -1
  148. pyglet/libs/darwin/cocoapy/__init__.py +2 -2
  149. pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
  150. pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
  151. pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
  152. pyglet/libs/darwin/cocoapy/runtime.py +81 -45
  153. pyglet/libs/darwin/coreaudio.py +4 -4
  154. pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
  155. pyglet/libs/darwin/quartzkey.py +1 -3
  156. pyglet/libs/egl/__init__.py +2 -0
  157. pyglet/libs/egl/egl_lib.py +576 -0
  158. pyglet/libs/egl/eglext.py +51 -5
  159. pyglet/libs/linux/__init__.py +0 -0
  160. pyglet/libs/linux/egl/__init__.py +0 -0
  161. pyglet/libs/linux/egl/eglext.py +22 -0
  162. pyglet/libs/linux/glx/__init__.py +0 -0
  163. pyglet/{gl → libs/linux/glx}/glx.py +13 -14
  164. pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
  165. pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
  166. pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
  167. pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
  168. pyglet/libs/linux/wayland/__init__.py +0 -0
  169. pyglet/libs/linux/wayland/client.py +1068 -0
  170. pyglet/libs/linux/wayland/lib_wayland.py +207 -0
  171. pyglet/libs/linux/wayland/wayland_egl.py +38 -0
  172. pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
  173. pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
  174. pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
  175. pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
  176. pyglet/libs/linux/x11/xrandr.py +0 -0
  177. pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
  178. pyglet/libs/shared/__init__.py +0 -0
  179. pyglet/libs/shared/spirv/__init__.py +0 -0
  180. pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
  181. pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
  182. pyglet/libs/win32/__init__.py +27 -5
  183. pyglet/libs/win32/constants.py +59 -48
  184. pyglet/libs/win32/context_managers.py +20 -3
  185. pyglet/libs/win32/dinput.py +105 -88
  186. pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
  187. pyglet/libs/win32/types.py +58 -23
  188. pyglet/{gl → libs/win32}/wgl.py +32 -25
  189. pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
  190. pyglet/media/__init__.py +9 -10
  191. pyglet/media/codecs/__init__.py +12 -1
  192. pyglet/media/codecs/base.py +99 -96
  193. pyglet/media/codecs/ffmpeg.py +2 -2
  194. pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
  195. pyglet/media/codecs/webaudio_pyodide.py +111 -0
  196. pyglet/media/drivers/__init__.py +9 -4
  197. pyglet/media/drivers/base.py +4 -4
  198. pyglet/media/drivers/openal/__init__.py +1 -1
  199. pyglet/media/drivers/openal/adaptation.py +3 -3
  200. pyglet/media/drivers/pulse/__init__.py +1 -1
  201. pyglet/media/drivers/pulse/adaptation.py +3 -3
  202. pyglet/media/drivers/pyodide_js/__init__.py +8 -0
  203. pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
  204. pyglet/media/drivers/xaudio2/adaptation.py +3 -3
  205. pyglet/media/player.py +276 -193
  206. pyglet/media/player_worker_thread.py +1 -1
  207. pyglet/model/__init__.py +39 -29
  208. pyglet/model/codecs/base.py +4 -4
  209. pyglet/model/codecs/gltf.py +3 -3
  210. pyglet/model/codecs/obj.py +71 -43
  211. pyglet/resource.py +129 -78
  212. pyglet/shapes.py +147 -177
  213. pyglet/sprite.py +47 -164
  214. pyglet/text/__init__.py +44 -54
  215. pyglet/text/caret.py +12 -7
  216. pyglet/text/document.py +19 -17
  217. pyglet/text/formats/html.py +2 -2
  218. pyglet/text/formats/structured.py +10 -40
  219. pyglet/text/layout/__init__.py +20 -13
  220. pyglet/text/layout/base.py +176 -287
  221. pyglet/text/layout/incremental.py +9 -10
  222. pyglet/text/layout/scrolling.py +7 -95
  223. pyglet/window/__init__.py +183 -172
  224. pyglet/window/cocoa/__init__.py +62 -51
  225. pyglet/window/cocoa/pyglet_delegate.py +2 -25
  226. pyglet/window/cocoa/pyglet_view.py +9 -8
  227. pyglet/window/dialog/__init__.py +184 -0
  228. pyglet/window/dialog/base.py +99 -0
  229. pyglet/window/dialog/darwin.py +121 -0
  230. pyglet/window/dialog/linux.py +72 -0
  231. pyglet/window/dialog/windows.py +194 -0
  232. pyglet/window/emscripten/__init__.py +779 -0
  233. pyglet/window/headless/__init__.py +44 -28
  234. pyglet/window/key.py +2 -0
  235. pyglet/window/mouse.py +2 -2
  236. pyglet/window/wayland/__init__.py +377 -0
  237. pyglet/window/win32/__init__.py +101 -46
  238. pyglet/window/xlib/__init__.py +104 -66
  239. {pyglet-2.1.12.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
  240. pyglet-3.0.dev1.dist-info/RECORD +322 -0
  241. {pyglet-2.1.12.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
  242. pyglet/gl/__init__.py +0 -208
  243. pyglet/gl/base.py +0 -499
  244. pyglet/gl/cocoa.py +0 -309
  245. pyglet/gl/gl.py +0 -4625
  246. pyglet/gl/gl.pyi +0 -2320
  247. pyglet/gl/gl_compat.pyi +0 -3097
  248. pyglet/gl/gl_info.py +0 -190
  249. pyglet/gl/headless.py +0 -166
  250. pyglet/gl/wgl_info.py +0 -36
  251. pyglet/gl/wglext_nv.py +0 -1096
  252. pyglet/gl/win32.py +0 -268
  253. pyglet/gl/xlib.py +0 -295
  254. pyglet/image/buffer.py +0 -274
  255. pyglet/image/codecs/s3tc.py +0 -354
  256. pyglet/libs/x11/xrandr.py +0 -166
  257. pyglet-2.1.12.dist-info/RECORD +0 -234
  258. /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
  259. /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
  260. /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
  261. /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
  262. /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
  263. /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
  264. /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
  265. /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
  266. /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
  267. {pyglet-2.1.12.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
pyglet/input/base.py CHANGED
@@ -9,6 +9,7 @@ import enum
9
9
  import warnings
10
10
 
11
11
  from typing import TYPE_CHECKING, Literal
12
+ from dataclasses import dataclass
12
13
 
13
14
  from pyglet.math import Vec2
14
15
  from pyglet.event import EventDispatcher
@@ -16,7 +17,6 @@ from pyglet.event import EventDispatcher
16
17
  if TYPE_CHECKING:
17
18
  from pyglet.window import BaseWindow
18
19
  from pyglet.display.base import Display
19
- from pyglet.input.controller import Relation
20
20
 
21
21
  _is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run
22
22
 
@@ -40,12 +40,19 @@ class Sign(enum.Enum):
40
40
  DEFAULT = enum.auto()
41
41
 
42
42
 
43
+ @dataclass
44
+ class Relation:
45
+ control_type: str
46
+ index: int
47
+ sign: Sign = Sign.DEFAULT
48
+
49
+
43
50
  class Device:
44
51
  """Low level input device."""
45
52
 
46
53
  connected: bool
47
54
 
48
- def __init__(self, display: Display, name: str) -> None:
55
+ def __init__(self, display: Display | None, name: str) -> None:
49
56
  """Create a Device to receive input from.
50
57
 
51
58
  Args:
@@ -82,7 +89,6 @@ class Device:
82
89
  If the device cannot be opened in exclusive mode, usually
83
90
  due to being opened exclusively by another application.
84
91
  """
85
-
86
92
  if self._is_open:
87
93
  raise DeviceOpenException('Device is already open.')
88
94
 
@@ -121,7 +127,7 @@ class Control(EventDispatcher):
121
127
  device; in some cases the control's value will be outside this range.
122
128
  """
123
129
 
124
- def __init__(self, name: None | str, raw_name: None | str = None, inverted: bool = False):
130
+ def __init__(self, name: None | str, raw_name: None | str = None, inverted: bool = False) -> None:
125
131
  """Create a Control to receive input.
126
132
 
127
133
  Args:
@@ -151,7 +157,7 @@ class Control(EventDispatcher):
151
157
  return self._value
152
158
 
153
159
  @value.setter
154
- def value(self, newvalue: float):
160
+ def value(self, newvalue: float) -> None:
155
161
  if newvalue == self._value:
156
162
  return
157
163
  self._value = newvalue
@@ -160,12 +166,10 @@ class Control(EventDispatcher):
160
166
  def __repr__(self) -> str:
161
167
  if self.name:
162
168
  return f"{self.__class__.__name__}(name={self.name}, raw_name={self.raw_name})"
163
- else:
164
- return f"{self.__class__.__name__}(raw_name={self.raw_name})"
169
+ return f"{self.__class__.__name__}(raw_name={self.raw_name})"
165
170
 
166
171
  # Events
167
-
168
- def on_change(self, value) -> float:
172
+ def on_change(self, value: float) -> bool:
169
173
  """The value changed."""
170
174
 
171
175
 
@@ -192,7 +196,7 @@ class RelativeAxis(Control):
192
196
  return self._value
193
197
 
194
198
  @value.setter
195
- def value(self, value: float):
199
+ def value(self, value: float) -> None:
196
200
  self._value = value
197
201
  self.dispatch_event('on_change', value)
198
202
 
@@ -213,21 +217,21 @@ class AbsoluteAxis(Control):
213
217
  HAT_X: str = 'hat_x'
214
218
  HAT_Y: str = 'hat_y'
215
219
 
216
- def __init__(self, name: str, minimum: float, maximum: float, raw_name: None | str = None, inverted: bool = False):
220
+ def __init__(self, name: str, minimum: float, maximum: float, raw_name: None | str = None, inverted: bool = False) -> None: # noqa: D107
217
221
  super().__init__(name, raw_name, inverted)
218
222
  self.min = minimum
219
223
  self.max = maximum
220
224
 
221
225
 
222
226
  class Button(Control):
223
- """A control whose value is boolean. """
227
+ """A control whose value is boolean."""
224
228
 
225
229
  @property
226
230
  def value(self) -> bool:
227
231
  return bool(self._value)
228
232
 
229
233
  @value.setter
230
- def value(self, newvalue: bool | int):
234
+ def value(self, newvalue: bool | int) -> None:
231
235
  if newvalue == self._value:
232
236
  return
233
237
  self._value = newvalue
@@ -252,8 +256,9 @@ Button.register_event_type('on_release')
252
256
 
253
257
 
254
258
  class Joystick(EventDispatcher):
255
- """High-level interface for joystick-like devices. This includes a wide
256
- range of analog and digital joysticks, gamepads, controllers, and possibly
259
+ """High-level interface for joystick-like devices.
260
+
261
+ This includes a wide range of analog and digital joysticks, gamepads, controllers, and possibly
257
262
  even steering wheels and other input devices. There is unfortunately no
258
263
  easy way to distinguish between most of these different device types.
259
264
 
@@ -263,7 +268,7 @@ class Joystick(EventDispatcher):
263
268
 
264
269
  To use a joystick, first call `open`, then in your game loop examine
265
270
  the values of `x`, `y`, and so on. These values are normalized to the
266
- range [-1.0, 1.0].
271
+ range [-1.0, 1.0].
267
272
 
268
273
  To receive events when the value of an axis changes, attach an
269
274
  on_joyaxis_motion event handler to the joystick. The :py:class:`~pyglet.input.Joystick`
@@ -275,7 +280,7 @@ class Joystick(EventDispatcher):
275
280
 
276
281
  Alternately, you may attach event handlers to each individual button in
277
282
  `button_controls` to receive on_press or on_release events.
278
-
283
+
279
284
  To use the hat switch, attach an on_joyhat_motion event handler to the joystick.
280
285
  The handler will be called with both the hat_x and hat_y values whenever the value
281
286
  of the hat switch changes.
@@ -356,7 +361,7 @@ class Joystick(EventDispatcher):
356
361
  self.hat_y_control = None
357
362
  self.button_controls = []
358
363
 
359
- def add_axis(control: AbsoluteAxis):
364
+ def add_axis(control: AbsoluteAxis) -> None:
360
365
  if not (control.min or control.max):
361
366
  warnings.warn(f"Control('{control.name}') min & max values are both 0. Skipping.")
362
367
  return
@@ -374,7 +379,7 @@ class Joystick(EventDispatcher):
374
379
  setattr(self, name, normalized_value)
375
380
  self.dispatch_event('on_joyaxis_motion', self, name, normalized_value)
376
381
 
377
- def add_button(control: Button):
382
+ def add_button(control: Button) -> None:
378
383
  i = len(self.buttons)
379
384
  self.buttons.append(False)
380
385
  self.button_controls.append(control)
@@ -391,7 +396,7 @@ class Joystick(EventDispatcher):
391
396
  def on_release():
392
397
  self.dispatch_event('on_joybutton_release', self, i)
393
398
 
394
- def add_hat(control: AbsoluteAxis):
399
+ def add_hat(control: AbsoluteAxis) -> None:
395
400
  # 8-directional hat encoded as a single control (Windows/Mac)
396
401
  self.hat_x_control = control
397
402
  self.hat_y_control = control
@@ -415,11 +420,11 @@ class Joystick(EventDispatcher):
415
420
  add_button(ctrl)
416
421
 
417
422
  def open(self, window: BaseWindow | None = None, exclusive: bool = False) -> None:
418
- """Open the joystick device. See `Device.open`. """
423
+ """Open the joystick device. See `Device.open`."""
419
424
  self.device.open(window, exclusive)
420
425
 
421
426
  def close(self) -> None:
422
- """Close the joystick device. See `Device.close`. """
427
+ """Close the joystick device. See `Device.close`."""
423
428
  self.device.close()
424
429
 
425
430
  # Events
@@ -491,7 +496,7 @@ class Controller(EventDispatcher):
491
496
  To use a Controller, you must first call ``open``. Controllers will then
492
497
  dispatch various events whenever the inputs change. They can also be polled
493
498
  manually at any time to find the current value of any inputs. Analog stick
494
- inputs are normalized to the range [-1.0, 1.0], and triggers are normalized
499
+ axis are normalized to the range [-1.0, 1.0], and triggers are normalized
495
500
  to the range [0.0, 1.0]. All other inputs are digital.
496
501
 
497
502
  Note: A running application event loop is required
@@ -499,11 +504,62 @@ class Controller(EventDispatcher):
499
504
  The following event types are dispatched:
500
505
  `on_button_press`
501
506
  `on_button_release`
502
- `on_stick_motion`
507
+ `on_leftstick_motion`
508
+ `on_rightstick_motion`
503
509
  `on_dpad_motion`
504
- `on_trigger_motion`
510
+ `on_lefttrigger_motion`
511
+ `on_righttrigger_motion`
505
512
 
506
513
  """
514
+ #: :The underlying device used by this Controller interface.
515
+ device: Device
516
+ #: The logical device name
517
+ name: str
518
+ #: The unique guid for this Device
519
+ guid: str
520
+
521
+ #: :The "south" face button.
522
+ a: bool
523
+ #: :The "east" face button.
524
+ b: bool
525
+ #: :The "west" fast button.
526
+ x: bool
527
+ #: :The "north" face button.
528
+ y: bool
529
+ #: :Sometimes called "share" or "select".
530
+ back: bool
531
+ #: :Sometimes called "options" or "menu".
532
+ start: bool
533
+ #: :The home button on the device.
534
+ guide: bool
535
+ #: :The top left bumper.
536
+ leftshoulder: bool
537
+ #: :The top right bumper.
538
+ rightshoulder: bool
539
+ #: :Pushing in on the left analog stick.
540
+ leftthumb: bool
541
+ #: :Pushing in on the right analog stick.
542
+ rightthumb: bool
543
+ #: :Left analog trigger.
544
+ lefttrigger: float
545
+ #: :Right analog trigger.
546
+ righttrigger: float
547
+ #: :The direction pad, as a 2D vector.
548
+ dpad: Vec2
549
+ #: :The left analog stick.
550
+ leftstick: Vec2
551
+ #: :The right analog stick.
552
+ rightstick: Vec2
553
+
554
+ #: :A list of all Button Controls exposed by the underlying device,
555
+ #: :regardless of whether they are bound to the Controller layout.
556
+ button_controls: list[Button]
557
+ #: :A list of all RelativeAxis Controls exposed by the underlying device,
558
+ #: :regardless of whether they are bound to the Controller layout.
559
+ relative_axis_controls: list[RelativeAxis]
560
+ #: :A list of all AbsoluteAxis Controls exposed by the underlying device,
561
+ #: :regardless of whether they are bound to the Controller layout.
562
+ absolute_axis_controls: list[AbsoluteAxis]
507
563
 
508
564
  def __init__(self, device: Device, mapping: dict):
509
565
  """Create a Controller instance mapped to a Device.
@@ -514,13 +570,10 @@ class Controller(EventDispatcher):
514
570
  #: The underlying Device:
515
571
  self.device: Device = device
516
572
  self._mapping = mapping
517
-
518
- #: The logical device name
519
573
  self.name: str = mapping.get('name')
520
- #: The unique guid for this Device
521
574
  self.guid: str = mapping.get('guid')
522
575
 
523
- # Pollable
576
+ # Pollable attributes
524
577
  self.a: bool = False
525
578
  self.b: bool = False
526
579
  self.x: bool = False
@@ -530,27 +583,27 @@ class Controller(EventDispatcher):
530
583
  self.guide: bool = False
531
584
  self.leftshoulder: bool = False
532
585
  self.rightshoulder: bool = False
533
- self.leftstick: bool = False # stick press button
534
- self.rightstick: bool = False # stick press button
535
-
586
+ self.leftthumb: bool = False
587
+ self.rightthumb: bool = False
536
588
  self.lefttrigger: float = 0.0
537
589
  self.righttrigger: float = 0.0
538
590
  self.dpad: Vec2 = Vec2()
539
- self.leftanalog: Vec2 = Vec2()
540
- self.rightanalog: Vec2 = Vec2()
541
-
542
- self.leftx: float = 0.0
543
- self.lefty: float = 0.0
544
- self.rightx: float = 0.0
545
- self.righty: float = 0.0
546
- self.dpadx: float = 0.0
547
- self.dpady: float = 0.0
548
-
549
- self._button_controls: list = []
550
- self._axis_controls: list = []
551
- self._hat_control: Control | None = None
552
- self._hat_x_control: Control | None = None
553
- self._hat_y_control: Control | None = None
591
+ self.leftstick: Vec2 = Vec2()
592
+ self.rightstick: Vec2 = Vec2()
593
+
594
+ # internal temp values that may be used when
595
+ # consolidating to the above final attributes
596
+ self._leftx: float = 0.0
597
+ self._lefty: float = 0.0
598
+ self._rightx: float = 0.0
599
+ self._righty: float = 0.0
600
+ self._dpadx: float = 0.0
601
+ self._dpady: float = 0.0
602
+
603
+ # ALL Controls this device exposes, whether they are mapped to the default layout or not:
604
+ self.button_controls = [c for c in self.device.get_controls() if isinstance(c, Button)]
605
+ self.relative_axis_controls = [c for c in self.device.get_controls() if isinstance(c, RelativeAxis)]
606
+ self.absolute_axis_controls = [c for c in self.device.get_controls() if isinstance(c, AbsoluteAxis)]
554
607
 
555
608
  self._initialize_controls()
556
609
 
@@ -597,7 +650,7 @@ class Controller(EventDispatcher):
597
650
 
598
651
  return 'GENERIC'
599
652
 
600
- def _bind_axis_control(self, relation: Relation, control: AbsoluteAxis, axis_name: str) -> None:
653
+ def _bind_axis_control(self, control: AbsoluteAxis, axis_name: str, sign_type: Sign) -> None:
601
654
  if not (control.min or control.max):
602
655
  warnings.warn(f"Control('{control.name}') min & max values are both 0. Skipping.")
603
656
  return
@@ -614,59 +667,74 @@ class Controller(EventDispatcher):
614
667
  dpad_defaults = {'dpup': Sign.POSITIVE, 'dpdown': Sign.NEGATIVE,
615
668
  'dpleft': Sign.NEGATIVE, 'dpright': Sign.POSITIVE}
616
669
 
617
- # If the sign is not DEFAULT, it must be inverted:
618
- if relation.sign not in (Sign.DEFAULT, dpad_defaults.get(axis_name)):
670
+ # If the sign is it's DEFAULT, it must be inverted:
671
+ if sign_type not in (Sign.DEFAULT, dpad_defaults.get(axis_name)):
619
672
  sign = -1.0
620
673
 
621
674
  if axis_name in ("dpup", "dpdown"):
622
675
  @control.event
623
676
  def on_change(value):
624
- self.dpady = round(value * scale + bias) * sign # normalized
625
- self.dpad = Vec2(self.dpadx, self.dpady)
626
- self.dispatch_event('on_dpad_motion', self, Vec2(self.dpadx, self.dpady))
677
+ self._dpady = round(value * scale + bias) * sign # normalized
678
+ self.dpad = Vec2(self._dpadx, self._dpady)
679
+ self.dispatch_event('on_dpad_motion', self, Vec2(self._dpadx, self._dpady))
627
680
 
628
681
  elif axis_name in ("dpleft", "dpright"):
629
682
  @control.event
630
683
  def on_change(value):
631
- self.dpadx = round(value * scale + bias) * sign # normalized
632
- self.dpad = Vec2(self.dpadx, self.dpady)
633
- self.dispatch_event('on_dpad_motion', self, Vec2(self.dpadx, self.dpady))
684
+ self._dpadx = round(value * scale + bias) * sign # normalized
685
+ self.dpad = Vec2(self._dpadx, self._dpady)
686
+ self.dispatch_event('on_dpad_motion', self, Vec2(self._dpadx, self._dpady))
634
687
 
635
- elif axis_name in ("lefttrigger", "righttrigger"):
688
+ elif axis_name == "lefttrigger":
636
689
  @control.event
637
690
  def on_change(value):
638
691
  normalized_value = value * tscale
639
692
  setattr(self, axis_name, normalized_value)
640
- self.dispatch_event('on_trigger_motion', self, axis_name, normalized_value)
693
+ self.dispatch_event(f'on_lefttrigger_motion', self, normalized_value)
641
694
 
642
- elif axis_name in ("leftx", "lefty"):
695
+ elif axis_name == "righttrigger":
643
696
  @control.event
644
697
  def on_change(value):
645
- normalized_value = value * scale + bias
698
+ normalized_value = value * tscale
646
699
  setattr(self, axis_name, normalized_value)
647
- self.left_analog = Vec2(self.leftx, -self.lefty)
648
- self.dispatch_event('on_stick_motion', self, "leftstick", self.left_analog)
700
+ self.dispatch_event(f'on_righttrigger_motion', self, normalized_value)
701
+
702
+ elif axis_name in ("leftx", "lefty"):
703
+ attrname = f"_{axis_name}"
704
+
705
+ @control.event
706
+ def on_change(value):
707
+ setattr(self, attrname, value * scale + bias) # normalized value
708
+ self.leftstick = Vec2(self._leftx, -self._lefty)
709
+ self.dispatch_event('on_leftstick_motion', self, self.leftstick)
649
710
 
650
711
  elif axis_name in ("rightx", "righty"):
712
+ attrname = f"_{axis_name}"
713
+
651
714
  @control.event
652
715
  def on_change(value):
653
- normalized_value = value * scale + bias
654
- setattr(self, axis_name, normalized_value)
655
- self.right_analog = Vec2(self.rightx, -self.righty)
656
- self.dispatch_event('on_stick_motion', self, "rightstick", self.right_analog)
716
+ setattr(self, attrname, value * scale + bias) # normalized value
717
+ self.right_analog = Vec2(self._rightx, -self._righty)
718
+ self.dispatch_event('on_rightstick_motion', self, self.right_analog)
657
719
 
658
- def _bind_button_control(self, relation: Relation, control: Button, button_name: str) -> None:
720
+ def _bind_button_control(self, control: Button, button_name: str) -> None:
659
721
  if button_name in ("dpleft", "dpright", "dpup", "dpdown"):
660
- defaults = {'dpleft': ('dpadx', -1.0), 'dpright': ('dpadx', 1.0),
661
- 'dpdown': ('dpady', -1.0), 'dpup': ('dpady', 1.0)}
722
+ # This is a button that is mapped to a dpad direction:
723
+
724
+ # mapping of {button_name: (_scratch_attr, axis_value)}
725
+ name_to_axis = {'dpleft': ('_dpadx', -1.0), 'dpright': ('_dpadx', 1.0),
726
+ 'dpdown': ('_dpady', -1.0), 'dpup': ('_dpady', 1.0)}
662
727
 
663
728
  @control.event
664
729
  def on_change(value):
665
- target, bias = defaults[button_name]
666
- setattr(self, target, bias * value)
667
- self.dpad = Vec2(self.dpadx, self.dpady)
730
+ attrname, bias = name_to_axis[button_name]
731
+ setattr(self, attrname, bias * value)
732
+ self.dpad = Vec2(self._dpadx, self._dpady)
668
733
  self.dispatch_event('on_dpad_motion', self, self.dpad)
734
+
669
735
  else:
736
+ # This is a regular button:
737
+
670
738
  @control.event
671
739
  def on_change(value):
672
740
  setattr(self, button_name, value)
@@ -679,7 +747,7 @@ class Controller(EventDispatcher):
679
747
  def on_release():
680
748
  self.dispatch_event('on_button_release', self, button_name)
681
749
 
682
- def _bind_dedicated_hat(self, relation: Relation, control: AbsoluteAxis) -> None:
750
+ def _bind_dedicated_hat(self, control: AbsoluteAxis) -> None:
683
751
  # 8-directional hat encoded as a single control (Windows/Mac)
684
752
  _vecs = (Vec2(0.0, 1.0), Vec2(1.0, 1.0), Vec2(1.0, 0.0), Vec2(1.0, -1.0), # n, ne, e, se
685
753
  Vec2(0.0, -1.0), Vec2(-1.0, -1.0), Vec2(-1.0, 0.0), Vec2(-1.0, 1.0)) # s, sw, w, nw
@@ -690,9 +758,8 @@ class Controller(EventDispatcher):
690
758
 
691
759
  @control.event
692
760
  def on_change(value):
693
- vector = _input_map.get(value // _scale, Vec2(0.0, 0.0))
694
- self.dpad = vector
695
- self.dispatch_event('on_dpad_motion', self, vector)
761
+ self.dpad = _input_map.get(value // _scale, Vec2(0.0, 0.0))
762
+ self.dispatch_event('on_dpad_motion', self, self.dpad)
696
763
 
697
764
  def _initialize_controls(self) -> None:
698
765
  """Initialize and bind the Device Controls
@@ -701,44 +768,33 @@ class Controller(EventDispatcher):
701
768
  then binds them to the appropriate "virtual" controls
702
769
  as defined in the mapped relations.
703
770
  """
704
-
705
- for ctrl in self.device.get_controls():
706
- # Categorize the various control types
707
- if isinstance(ctrl, Button):
708
- self._button_controls.append(ctrl)
709
-
710
- elif isinstance(ctrl, AbsoluteAxis):
711
- if ctrl.name == "hat_x":
712
- self._hat_x_control = ctrl
713
- elif ctrl.name == "hat_y":
714
- self._hat_y_control = ctrl
715
- elif ctrl.name == "hat":
716
- self._hat_control = ctrl
717
- else:
718
- self._axis_controls.append(ctrl)
771
+ hat_control = next((c for c in self.absolute_axis_controls if c.name == 'hat'), None)
772
+ hat_x_control = next((c for c in self.absolute_axis_controls if c.name == 'hat_x'), None)
773
+ hat_y_control = next((c for c in self.absolute_axis_controls if c.name == 'hat_y'), None)
719
774
 
720
775
  for name, relation in self._mapping.items():
721
-
722
- if relation is None or isinstance(relation, str):
776
+ if not isinstance(relation, Relation):
723
777
  continue
724
778
 
725
779
  try:
726
780
  if relation.control_type == "button":
727
- self._bind_button_control(relation, self._button_controls[relation.index], name)
781
+ self._bind_button_control(self.button_controls[relation.index], name)
728
782
 
729
783
  elif relation.control_type == "axis":
730
- self._bind_axis_control(relation, self._axis_controls[relation.index], name)
784
+ self._bind_axis_control(self.absolute_axis_controls[relation.index], name, relation.sign)
731
785
 
732
786
  elif relation.control_type == "hat0":
733
- if self._hat_control:
734
- self._bind_dedicated_hat(relation, self._hat_control)
735
- else:
736
- control, dpname = {1: (self._hat_y_control, 'dpup'),
737
- 2: (self._hat_x_control, 'dpright'),
738
- 4: (self._hat_y_control, 'dpdown'),
739
- 8: (self._hat_x_control, 'dpleft')}[relation.index]
787
+ if hat_control:
788
+ self._bind_dedicated_hat(hat_control)
789
+ continue
790
+
791
+ elif hat_x_control and hat_y_control:
792
+ control, dpname = {1: (hat_y_control, 'dpup'),
793
+ 2: (hat_x_control, 'dpright'),
794
+ 4: (hat_y_control, 'dpdown'),
795
+ 8: (hat_x_control, 'dpleft')}[relation.index]
740
796
 
741
- self._bind_axis_control(relation, control, dpname)
797
+ self._bind_axis_control(control, dpname, relation.sign)
742
798
 
743
799
  except (IndexError, AttributeError, KeyError):
744
800
  warnings.warn(f"Could not map physical Control '{relation}' to '{name}'")
@@ -842,9 +898,11 @@ class Controller(EventDispatcher):
842
898
 
843
899
  Controller.register_event_type('on_button_press')
844
900
  Controller.register_event_type('on_button_release')
845
- Controller.register_event_type('on_stick_motion')
901
+ Controller.register_event_type('on_leftstick_motion')
902
+ Controller.register_event_type('on_rightstick_motion')
846
903
  Controller.register_event_type('on_dpad_motion')
847
- Controller.register_event_type('on_trigger_motion')
904
+ Controller.register_event_type('on_lefttrigger_motion')
905
+ Controller.register_event_type('on_righttrigger_motion')
848
906
 
849
907
 
850
908
  class AppleRemote(EventDispatcher):
@@ -26,7 +26,7 @@ import os as _os
26
26
  import sys as _sys
27
27
  import warnings as _warnings
28
28
 
29
- from .base import Sign
29
+ from .base import Sign, Relation
30
30
  from .controller_db import mapping_list
31
31
 
32
32
 
@@ -53,18 +53,6 @@ def create_guid(bus: int, vendor: int, product: int, version: int, name: str, si
53
53
  return f"{bus:04x}0000{vendor:04x}0000{product:04x}0000{version:04x}0000"
54
54
 
55
55
 
56
- class Relation:
57
- __slots__ = 'control_type', 'index', 'sign'
58
-
59
- def __init__(self, control_type: str, index: int, sign: Sign = Sign.DEFAULT):
60
- self.control_type = control_type
61
- self.index = index
62
- self.sign = sign
63
-
64
- def __repr__(self):
65
- return f"Relation(type={self.control_type}, index={self.index}, sign={self.sign})"
66
-
67
-
68
56
  def _parse_mapping(mapping_string: str) -> dict[str, str | Relation] | None:
69
57
  """Parse a SDL2 style GameController mapping string.
70
58
 
@@ -79,7 +67,7 @@ def _parse_mapping(mapping_string: str) -> dict[str, str | Relation] | None:
79
67
 
80
68
  try:
81
69
  guid, name, *split_mapping = mapping_string.strip().split(",")
82
- relations = dict(guid=guid, name=name)
70
+ relations: dict[str, str | Relation] = dict(guid=guid, name=name)
83
71
  except ValueError:
84
72
  return None
85
73
 
@@ -88,11 +76,15 @@ def _parse_mapping(mapping_string: str) -> dict[str, str | Relation] | None:
88
76
  if ':' not in item:
89
77
  continue
90
78
 
91
- key, relation_string, *etc = item.split(':')
79
+ input_name, relation_string, *etc = item.split(':')
92
80
 
93
- if key not in valid_keys:
81
+ if input_name not in valid_keys:
94
82
  continue
95
83
 
84
+ # override some input_names to match pyglet layout:
85
+ input_name = {'leftstick': 'leftthumb',
86
+ 'rightstick': 'rightthumb'}.get(input_name, input_name)
87
+
96
88
  # Look for specific flags to signify axis sign:
97
89
  if "+" in relation_string:
98
90
  relation_string = relation_string.strip('+')
@@ -108,11 +100,11 @@ def _parse_mapping(mapping_string: str) -> dict[str, str | Relation] | None:
108
100
 
109
101
  # All relations will be one of (Button, Axis, or Hat).
110
102
  if relation_string.startswith("b"): # Button
111
- relations[key] = Relation("button", int(relation_string[1:]), sign)
103
+ relations[input_name] = Relation("button", index=int(relation_string[1:]), sign=sign)
112
104
  elif relation_string.startswith("a"): # Axis
113
- relations[key] = Relation("axis", int(relation_string[1:]), sign)
105
+ relations[input_name] = Relation("axis", index=int(relation_string[1:]), sign=sign)
114
106
  elif relation_string.startswith("h0"): # Hat
115
- relations[key] = Relation("hat0", int(relation_string.split(".")[1]), sign)
107
+ relations[input_name] = Relation("hat0", index=int(relation_string.split(".")[1]), sign=sign)
116
108
 
117
109
  return relations
118
110
 
@@ -133,6 +125,8 @@ def get_mapping(guid: str) -> dict[str, str | Relation] | None:
133
125
  _warnings.warn(f"Unable to parse Controller mapping: {mapping}")
134
126
  continue
135
127
 
128
+ return None
129
+
136
130
 
137
131
  def add_mappings_from_file(filename: str) -> None:
138
132
  """Add one or more mappings from a file.