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.
- pyglet/__init__.py +67 -61
- pyglet/__init__.pyi +15 -8
- pyglet/app/__init__.py +22 -13
- pyglet/app/async_app.py +212 -0
- pyglet/app/base.py +2 -1
- pyglet/app/{xlib.py → linux.py} +3 -3
- pyglet/config/__init__.py +101 -0
- pyglet/config/gl/__init__.py +30 -0
- pyglet/config/gl/egl.py +120 -0
- pyglet/config/gl/macos.py +262 -0
- pyglet/config/gl/windows.py +267 -0
- pyglet/config/gl/x11.py +142 -0
- pyglet/customtypes.py +43 -2
- pyglet/display/__init__.py +8 -6
- pyglet/display/base.py +3 -63
- pyglet/display/cocoa.py +12 -17
- pyglet/display/emscripten.py +39 -0
- pyglet/display/headless.py +23 -30
- pyglet/display/wayland.py +157 -0
- pyglet/display/win32.py +4 -17
- pyglet/display/xlib.py +19 -27
- pyglet/display/xlib_vidmoderestore.py +2 -2
- pyglet/enums.py +183 -0
- pyglet/event.py +0 -1
- pyglet/experimental/geoshader_sprite.py +15 -13
- pyglet/experimental/hidraw.py +6 -15
- pyglet/experimental/multitexture_sprite.py +31 -19
- pyglet/experimental/particles.py +13 -35
- pyglet/font/__init__.py +251 -85
- pyglet/font/base.py +116 -61
- pyglet/font/dwrite/__init__.py +349 -204
- pyglet/font/dwrite/dwrite_lib.py +27 -5
- pyglet/font/fontconfig.py +14 -6
- pyglet/font/freetype.py +138 -87
- pyglet/font/freetype_lib.py +19 -0
- pyglet/font/group.py +179 -0
- pyglet/font/harfbuzz/__init__.py +3 -3
- pyglet/font/pyodide_js.py +310 -0
- pyglet/font/quartz.py +319 -126
- pyglet/font/ttf.py +45 -3
- pyglet/font/user.py +14 -19
- pyglet/font/win32.py +45 -21
- pyglet/graphics/__init__.py +8 -787
- pyglet/graphics/allocation.py +115 -1
- pyglet/graphics/api/__init__.py +77 -0
- pyglet/graphics/api/base.py +299 -0
- pyglet/graphics/api/gl/__init__.py +58 -0
- pyglet/graphics/api/gl/base.py +24 -0
- pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
- pyglet/graphics/api/gl/cocoa/context.py +76 -0
- pyglet/graphics/api/gl/context.py +391 -0
- pyglet/graphics/api/gl/default_shaders.py +0 -0
- pyglet/graphics/api/gl/draw.py +627 -0
- pyglet/graphics/api/gl/egl/__init__.py +0 -0
- pyglet/graphics/api/gl/egl/context.py +92 -0
- pyglet/graphics/api/gl/enums.py +76 -0
- pyglet/graphics/api/gl/framebuffer.py +315 -0
- pyglet/graphics/api/gl/gl.py +5463 -0
- pyglet/graphics/api/gl/gl_info.py +188 -0
- pyglet/graphics/api/gl/global_opengl.py +226 -0
- pyglet/{gl → graphics/api/gl}/lib.py +34 -18
- pyglet/graphics/api/gl/shader.py +1476 -0
- pyglet/graphics/api/gl/shapes.py +55 -0
- pyglet/graphics/api/gl/sprite.py +102 -0
- pyglet/graphics/api/gl/state.py +219 -0
- pyglet/graphics/api/gl/text.py +190 -0
- pyglet/graphics/api/gl/texture.py +1526 -0
- pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
- pyglet/graphics/api/gl/vertexdomain.py +751 -0
- pyglet/graphics/api/gl/win32/__init__.py +0 -0
- pyglet/graphics/api/gl/win32/context.py +108 -0
- pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
- pyglet/graphics/api/gl/xlib/__init__.py +0 -0
- pyglet/graphics/api/gl/xlib/context.py +174 -0
- pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
- pyglet/graphics/api/gl1/__init__.py +0 -0
- pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
- pyglet/graphics/api/gl2/__init__.py +0 -0
- pyglet/graphics/api/gl2/buffer.py +320 -0
- pyglet/graphics/api/gl2/draw.py +600 -0
- pyglet/graphics/api/gl2/global_opengl.py +122 -0
- pyglet/graphics/api/gl2/shader.py +200 -0
- pyglet/graphics/api/gl2/shapes.py +51 -0
- pyglet/graphics/api/gl2/sprite.py +79 -0
- pyglet/graphics/api/gl2/text.py +175 -0
- pyglet/graphics/api/gl2/vertexdomain.py +364 -0
- pyglet/graphics/api/webgl/__init__.py +233 -0
- pyglet/graphics/api/webgl/buffer.py +302 -0
- pyglet/graphics/api/webgl/context.py +234 -0
- pyglet/graphics/api/webgl/draw.py +590 -0
- pyglet/graphics/api/webgl/enums.py +76 -0
- pyglet/graphics/api/webgl/framebuffer.py +360 -0
- pyglet/graphics/api/webgl/gl.py +1537 -0
- pyglet/graphics/api/webgl/gl_info.py +130 -0
- pyglet/graphics/api/webgl/shader.py +1346 -0
- pyglet/graphics/api/webgl/shapes.py +92 -0
- pyglet/graphics/api/webgl/sprite.py +102 -0
- pyglet/graphics/api/webgl/state.py +227 -0
- pyglet/graphics/api/webgl/text.py +187 -0
- pyglet/graphics/api/webgl/texture.py +1227 -0
- pyglet/graphics/api/webgl/vertexarray.py +54 -0
- pyglet/graphics/api/webgl/vertexdomain.py +616 -0
- pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
- pyglet/{image → graphics}/atlas.py +33 -32
- pyglet/graphics/base.py +10 -0
- pyglet/graphics/buffer.py +245 -0
- pyglet/graphics/draw.py +578 -0
- pyglet/graphics/framebuffer.py +26 -0
- pyglet/graphics/instance.py +178 -69
- pyglet/graphics/shader.py +267 -1553
- pyglet/graphics/state.py +83 -0
- pyglet/graphics/texture.py +703 -0
- pyglet/graphics/vertexdomain.py +695 -538
- pyglet/gui/ninepatch.py +10 -10
- pyglet/gui/widgets.py +120 -10
- pyglet/image/__init__.py +20 -1973
- pyglet/image/animation.py +12 -12
- pyglet/image/base.py +730 -0
- pyglet/image/codecs/__init__.py +9 -0
- pyglet/image/codecs/bmp.py +53 -30
- pyglet/image/codecs/dds.py +53 -31
- pyglet/image/codecs/gdiplus.py +38 -14
- pyglet/image/codecs/gdkpixbuf2.py +0 -2
- pyglet/image/codecs/js_image.py +99 -0
- pyglet/image/codecs/ktx2.py +161 -0
- pyglet/image/codecs/pil.py +1 -1
- pyglet/image/codecs/png.py +1 -1
- pyglet/image/codecs/wic.py +11 -2
- pyglet/info.py +26 -24
- pyglet/input/__init__.py +8 -0
- pyglet/input/base.py +163 -105
- pyglet/input/controller.py +13 -19
- pyglet/input/controller_db.py +39 -24
- pyglet/input/emscripten/__init__.py +18 -0
- pyglet/input/emscripten/gamepad_js.py +397 -0
- pyglet/input/linux/__init__.py +11 -5
- pyglet/input/linux/evdev.py +10 -11
- pyglet/input/linux/x11_xinput.py +2 -2
- pyglet/input/linux/x11_xinput_tablet.py +1 -1
- pyglet/input/macos/__init__.py +7 -2
- pyglet/input/macos/darwin_gc.py +559 -0
- pyglet/input/win32/__init__.py +1 -1
- pyglet/input/win32/directinput.py +34 -29
- pyglet/input/win32/xinput.py +11 -61
- pyglet/lib.py +3 -3
- pyglet/libs/__init__.py +1 -1
- pyglet/{gl → libs/darwin}/agl.py +1 -1
- pyglet/libs/darwin/cocoapy/__init__.py +2 -2
- pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
- pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
- pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
- pyglet/libs/darwin/cocoapy/runtime.py +81 -45
- pyglet/libs/darwin/coreaudio.py +4 -4
- pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
- pyglet/libs/darwin/quartzkey.py +1 -3
- pyglet/libs/egl/__init__.py +2 -0
- pyglet/libs/egl/egl_lib.py +576 -0
- pyglet/libs/egl/eglext.py +51 -5
- pyglet/libs/linux/__init__.py +0 -0
- pyglet/libs/linux/egl/__init__.py +0 -0
- pyglet/libs/linux/egl/eglext.py +22 -0
- pyglet/libs/linux/glx/__init__.py +0 -0
- pyglet/{gl → libs/linux/glx}/glx.py +13 -14
- pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
- pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
- pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
- pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
- pyglet/libs/linux/wayland/__init__.py +0 -0
- pyglet/libs/linux/wayland/client.py +1068 -0
- pyglet/libs/linux/wayland/lib_wayland.py +207 -0
- pyglet/libs/linux/wayland/wayland_egl.py +38 -0
- pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
- pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
- pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
- pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
- pyglet/libs/linux/x11/xrandr.py +0 -0
- pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
- pyglet/libs/shared/__init__.py +0 -0
- pyglet/libs/shared/spirv/__init__.py +0 -0
- pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
- pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
- pyglet/libs/win32/__init__.py +27 -5
- pyglet/libs/win32/constants.py +59 -48
- pyglet/libs/win32/context_managers.py +20 -3
- pyglet/libs/win32/dinput.py +105 -88
- pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
- pyglet/libs/win32/types.py +58 -23
- pyglet/{gl → libs/win32}/wgl.py +32 -25
- pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
- pyglet/media/__init__.py +9 -10
- pyglet/media/codecs/__init__.py +12 -1
- pyglet/media/codecs/base.py +99 -96
- pyglet/media/codecs/ffmpeg.py +2 -2
- pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
- pyglet/media/codecs/webaudio_pyodide.py +111 -0
- pyglet/media/drivers/__init__.py +9 -4
- pyglet/media/drivers/base.py +4 -4
- pyglet/media/drivers/openal/__init__.py +1 -1
- pyglet/media/drivers/openal/adaptation.py +3 -3
- pyglet/media/drivers/pulse/__init__.py +1 -1
- pyglet/media/drivers/pulse/adaptation.py +3 -3
- pyglet/media/drivers/pyodide_js/__init__.py +8 -0
- pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
- pyglet/media/drivers/xaudio2/adaptation.py +3 -3
- pyglet/media/player.py +276 -193
- pyglet/media/player_worker_thread.py +1 -1
- pyglet/model/__init__.py +39 -29
- pyglet/model/codecs/base.py +4 -4
- pyglet/model/codecs/gltf.py +3 -3
- pyglet/model/codecs/obj.py +71 -43
- pyglet/resource.py +129 -78
- pyglet/shapes.py +147 -177
- pyglet/sprite.py +47 -164
- pyglet/text/__init__.py +44 -54
- pyglet/text/caret.py +12 -7
- pyglet/text/document.py +19 -17
- pyglet/text/formats/html.py +2 -2
- pyglet/text/formats/structured.py +10 -40
- pyglet/text/layout/__init__.py +20 -13
- pyglet/text/layout/base.py +176 -287
- pyglet/text/layout/incremental.py +9 -10
- pyglet/text/layout/scrolling.py +7 -95
- pyglet/window/__init__.py +183 -172
- pyglet/window/cocoa/__init__.py +62 -51
- pyglet/window/cocoa/pyglet_delegate.py +2 -25
- pyglet/window/cocoa/pyglet_view.py +9 -8
- pyglet/window/dialog/__init__.py +184 -0
- pyglet/window/dialog/base.py +99 -0
- pyglet/window/dialog/darwin.py +121 -0
- pyglet/window/dialog/linux.py +72 -0
- pyglet/window/dialog/windows.py +194 -0
- pyglet/window/emscripten/__init__.py +779 -0
- pyglet/window/headless/__init__.py +44 -28
- pyglet/window/key.py +2 -0
- pyglet/window/mouse.py +2 -2
- pyglet/window/wayland/__init__.py +377 -0
- pyglet/window/win32/__init__.py +101 -46
- pyglet/window/xlib/__init__.py +104 -66
- {pyglet-2.1.12.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
- pyglet-3.0.dev1.dist-info/RECORD +322 -0
- {pyglet-2.1.12.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
- pyglet/gl/__init__.py +0 -208
- pyglet/gl/base.py +0 -499
- pyglet/gl/cocoa.py +0 -309
- pyglet/gl/gl.py +0 -4625
- pyglet/gl/gl.pyi +0 -2320
- pyglet/gl/gl_compat.pyi +0 -3097
- pyglet/gl/gl_info.py +0 -190
- pyglet/gl/headless.py +0 -166
- pyglet/gl/wgl_info.py +0 -36
- pyglet/gl/wglext_nv.py +0 -1096
- pyglet/gl/win32.py +0 -268
- pyglet/gl/xlib.py +0 -295
- pyglet/image/buffer.py +0 -274
- pyglet/image/codecs/s3tc.py +0 -354
- pyglet/libs/x11/xrandr.py +0 -166
- pyglet-2.1.12.dist-info/RECORD +0 -234
- /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
- /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
- /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
- /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
- /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
- {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
|
-
|
|
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.
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
507
|
+
`on_leftstick_motion`
|
|
508
|
+
`on_rightstick_motion`
|
|
503
509
|
`on_dpad_motion`
|
|
504
|
-
`
|
|
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.
|
|
534
|
-
self.
|
|
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.
|
|
540
|
-
self.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
self.
|
|
545
|
-
self.
|
|
546
|
-
self.
|
|
547
|
-
self.
|
|
548
|
-
|
|
549
|
-
self.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
self.
|
|
553
|
-
self.
|
|
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,
|
|
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
|
|
618
|
-
if
|
|
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.
|
|
625
|
-
self.dpad = Vec2(self.
|
|
626
|
-
self.dispatch_event('on_dpad_motion', self, Vec2(self.
|
|
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.
|
|
632
|
-
self.dpad = Vec2(self.
|
|
633
|
-
self.dispatch_event('on_dpad_motion', self, Vec2(self.
|
|
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
|
|
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('
|
|
693
|
+
self.dispatch_event(f'on_lefttrigger_motion', self, normalized_value)
|
|
641
694
|
|
|
642
|
-
elif axis_name
|
|
695
|
+
elif axis_name == "righttrigger":
|
|
643
696
|
@control.event
|
|
644
697
|
def on_change(value):
|
|
645
|
-
normalized_value = value *
|
|
698
|
+
normalized_value = value * tscale
|
|
646
699
|
setattr(self, axis_name, normalized_value)
|
|
647
|
-
self.
|
|
648
|
-
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
self.
|
|
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,
|
|
720
|
+
def _bind_button_control(self, control: Button, button_name: str) -> None:
|
|
659
721
|
if button_name in ("dpleft", "dpright", "dpup", "dpdown"):
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
666
|
-
setattr(self,
|
|
667
|
-
self.dpad = Vec2(self.
|
|
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,
|
|
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
|
-
|
|
694
|
-
self.
|
|
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
|
|
706
|
-
|
|
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(
|
|
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(
|
|
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
|
|
734
|
-
self._bind_dedicated_hat(
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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(
|
|
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('
|
|
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('
|
|
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):
|
pyglet/input/controller.py
CHANGED
|
@@ -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
|
-
|
|
79
|
+
input_name, relation_string, *etc = item.split(':')
|
|
92
80
|
|
|
93
|
-
if
|
|
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[
|
|
103
|
+
relations[input_name] = Relation("button", index=int(relation_string[1:]), sign=sign)
|
|
112
104
|
elif relation_string.startswith("a"): # Axis
|
|
113
|
-
relations[
|
|
105
|
+
relations[input_name] = Relation("axis", index=int(relation_string[1:]), sign=sign)
|
|
114
106
|
elif relation_string.startswith("h0"): # Hat
|
|
115
|
-
relations[
|
|
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.
|