pyglet 2.1.dev3__py3-none-any.whl → 2.1.dev5__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 +55 -29
- pyglet/__init__.pyi +38 -0
- pyglet/app/win32.py +1 -1
- pyglet/display/__init__.py +4 -2
- pyglet/display/cocoa.py +4 -4
- pyglet/display/headless.py +1 -1
- pyglet/display/win32.py +22 -2
- pyglet/display/xlib.py +13 -9
- pyglet/event.py +21 -14
- pyglet/experimental/multitexture_sprite.py +520 -0
- pyglet/experimental/particles.py +73 -39
- pyglet/font/__init__.py +4 -6
- pyglet/font/directwrite.py +5 -4
- pyglet/font/freetype.py +3 -3
- pyglet/font/freetype_lib.py +3 -3
- pyglet/font/quartz.py +1 -1
- pyglet/font/win32.py +4 -4
- pyglet/gl/__init__.py +23 -16
- pyglet/gl/agl.py +1 -1
- pyglet/gl/base.py +66 -37
- pyglet/gl/cocoa.py +69 -61
- pyglet/gl/gl.py +5 -2
- pyglet/gl/gl_compat.py +18 -3
- pyglet/gl/gl_info.py +47 -64
- pyglet/gl/glx.py +10 -19
- pyglet/gl/glx_info.py +38 -19
- pyglet/gl/glxext_arb.py +6 -10
- pyglet/gl/glxext_mesa.py +1 -1
- pyglet/gl/glxext_nv.py +4 -8
- pyglet/gl/headless.py +54 -40
- pyglet/gl/lib.py +21 -17
- pyglet/gl/lib_agl.py +10 -4
- pyglet/gl/lib_glx.py +16 -8
- pyglet/gl/lib_wgl.py +17 -11
- pyglet/gl/wgl_info.py +10 -12
- pyglet/gl/win32.py +77 -56
- pyglet/gl/xlib.py +63 -32
- pyglet/graphics/__init__.py +280 -152
- pyglet/graphics/allocation.py +61 -81
- pyglet/graphics/instance.py +77 -0
- pyglet/graphics/shader.py +814 -459
- pyglet/graphics/vertexarray.py +20 -11
- pyglet/graphics/vertexbuffer.py +106 -78
- pyglet/graphics/vertexdomain.py +556 -194
- pyglet/image/__init__.py +107 -88
- pyglet/image/codecs/__init__.py +9 -8
- pyglet/lib.py +53 -55
- pyglet/libs/win32/__init__.py +1 -1
- pyglet/libs/win32/dinput.py +12 -10
- pyglet/math.py +936 -422
- pyglet/resource.py +2 -2
- pyglet/shapes.py +697 -525
- pyglet/text/__init__.py +23 -4
- pyglet/text/caret.py +4 -4
- pyglet/text/document.py +16 -16
- pyglet/text/formats/html.py +4 -4
- pyglet/text/formats/structured.py +3 -3
- pyglet/text/layout/base.py +55 -36
- pyglet/window/__init__.py +21 -0
- pyglet/window/cocoa/pyglet_textview.py +8 -0
- pyglet/window/win32/__init__.py +5 -13
- pyglet/window/xlib/__init__.py +1 -0
- {pyglet-2.1.dev3.dist-info → pyglet-2.1.dev5.dist-info}/METADATA +7 -3
- {pyglet-2.1.dev3.dist-info → pyglet-2.1.dev5.dist-info}/RECORD +66 -64
- {pyglet-2.1.dev3.dist-info → pyglet-2.1.dev5.dist-info}/LICENSE +0 -0
- {pyglet-2.1.dev3.dist-info → pyglet-2.1.dev5.dist-info}/WHEEL +0 -0
pyglet/__init__.py
CHANGED
|
@@ -6,12 +6,16 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
8
|
import sys
|
|
9
|
-
|
|
9
|
+
from collections.abc import ItemsView, Sequence
|
|
10
10
|
from dataclasses import dataclass
|
|
11
|
-
from typing import TYPE_CHECKING
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from types import FrameType
|
|
15
|
+
from typing import Any, Callable, ItemsView, Sized
|
|
12
16
|
|
|
13
17
|
#: The release version
|
|
14
|
-
version = '2.1.
|
|
18
|
+
version = '2.1.dev5'
|
|
15
19
|
__version__ = version
|
|
16
20
|
|
|
17
21
|
MIN_PYTHON_VERSION = 3, 8
|
|
@@ -34,11 +38,21 @@ if getattr(sys, "frozen", None):
|
|
|
34
38
|
_enable_optimisations = True
|
|
35
39
|
|
|
36
40
|
|
|
41
|
+
_SPECIAL_OPTION_VALIDATORS = {
|
|
42
|
+
"audio": lambda x: isinstance(x, Sequence),
|
|
43
|
+
"vsync": lambda x: x is None or isinstance(x, bool),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_OPTION_TYPE_VALIDATORS = {
|
|
47
|
+
"bool": lambda x: isinstance(x, bool),
|
|
48
|
+
"int": lambda x: isinstance(x, int),
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
@dataclass
|
|
38
52
|
class Options:
|
|
39
53
|
"""Dataclass for global pyglet options."""
|
|
40
54
|
|
|
41
|
-
audio:
|
|
55
|
+
audio: Sequence[str] = ("xaudio2", "directsound", "openal", "pulse", "silent")
|
|
42
56
|
"""A :py:class:`~typing.Sequence` of valid audio modules names. They will
|
|
43
57
|
be tried from first to last until either a driver loads or no entries
|
|
44
58
|
remain. See :ref:`guide-audio-driver-order` for more information.
|
|
@@ -122,12 +136,13 @@ class Options:
|
|
|
122
136
|
must be loaded after the window using them was created). Recommended
|
|
123
137
|
for advanced developers only.
|
|
124
138
|
|
|
125
|
-
.. versionadded:: 1.1
|
|
139
|
+
.. versionadded:: 1.1
|
|
140
|
+
"""
|
|
126
141
|
|
|
127
142
|
vsync: bool | None = None
|
|
128
|
-
"""If set
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
"""If set, the `pyglet.window.Window.vsync` property is ignored, and
|
|
144
|
+
this option overrides it (to either force vsync on or off). If unset,
|
|
145
|
+
or set to None, the `pyglet.window.Window.vsync` property behaves
|
|
131
146
|
as documented.
|
|
132
147
|
"""
|
|
133
148
|
|
|
@@ -170,7 +185,7 @@ class Options:
|
|
|
170
185
|
|
|
171
186
|
headless_device: int = 0
|
|
172
187
|
"""If using ``headless`` mode (``pyglet.options['headless'] = True``), this option allows you to set which
|
|
173
|
-
GPU to use. This is only useful on multi-GPU systems.
|
|
188
|
+
GPU to use. This is only useful on multi-GPU systems.
|
|
174
189
|
"""
|
|
175
190
|
|
|
176
191
|
win32_disable_shaping: bool = False
|
|
@@ -246,6 +261,16 @@ class Options:
|
|
|
246
261
|
number of pixels requested.
|
|
247
262
|
"""
|
|
248
263
|
|
|
264
|
+
shader_bind_management: bool = True
|
|
265
|
+
"""If ``True``, this will enable internal management of Uniform Block bindings for
|
|
266
|
+
:py:class:`~pyglet.graphics.shader.ShaderProgram`'s.
|
|
267
|
+
|
|
268
|
+
If ``False``, bindings will not be managed by Pyglet. The user will be responsible for either setting the binding
|
|
269
|
+
points through GLSL layouts (4.2 required) or manually through ``UniformBlock.set_binding``.
|
|
270
|
+
|
|
271
|
+
.. versionadded:: 2.0.16
|
|
272
|
+
"""
|
|
273
|
+
|
|
249
274
|
def get(self, item: str, default: Any = None) -> Any:
|
|
250
275
|
return self.__dict__.get(item, default)
|
|
251
276
|
|
|
@@ -257,12 +282,13 @@ class Options:
|
|
|
257
282
|
|
|
258
283
|
def __setitem__(self, key: str, value: Any) -> None:
|
|
259
284
|
assert key in self.__annotations__, f"Invalid option name: '{key}'"
|
|
260
|
-
assert
|
|
285
|
+
assert (_SPECIAL_OPTION_VALIDATORS.get(key, None) or _OPTION_TYPE_VALIDATORS[self.__annotations__[key]])(value), \
|
|
286
|
+
f"Invalid type: '{type(value)}' for '{key}'"
|
|
261
287
|
self.__dict__[key] = value
|
|
262
288
|
|
|
263
289
|
|
|
264
290
|
#: Instance of :py:class:`~pyglet.Options` used to set runtime options.
|
|
265
|
-
options = Options()
|
|
291
|
+
options: Options = Options()
|
|
266
292
|
|
|
267
293
|
|
|
268
294
|
for _key, _type in options.__annotations__.items():
|
|
@@ -282,10 +308,10 @@ if compat_platform == "cygwin":
|
|
|
282
308
|
# DirectSound support.
|
|
283
309
|
import ctypes
|
|
284
310
|
|
|
285
|
-
ctypes.windll = ctypes.cdll
|
|
286
|
-
ctypes.oledll = ctypes.cdll
|
|
311
|
+
ctypes.windll = ctypes.cdll
|
|
312
|
+
ctypes.oledll = ctypes.cdll
|
|
287
313
|
ctypes.WINFUNCTYPE = ctypes.CFUNCTYPE
|
|
288
|
-
ctypes.HRESULT = ctypes.c_long
|
|
314
|
+
ctypes.HRESULT = ctypes.c_long
|
|
289
315
|
|
|
290
316
|
# Call tracing
|
|
291
317
|
# ------------
|
|
@@ -297,17 +323,17 @@ _trace_depth = options["debug_trace_depth"]
|
|
|
297
323
|
_trace_flush = options["debug_trace_flush"]
|
|
298
324
|
|
|
299
325
|
|
|
300
|
-
def _trace_repr(value, size=40):
|
|
326
|
+
def _trace_repr(value: Sized, size: int=40) -> str:
|
|
301
327
|
value = repr(value)
|
|
302
328
|
if len(value) > size:
|
|
303
329
|
value = value[:size // 2 - 2] + "..." + value[-size // 2 - 1:]
|
|
304
330
|
return value
|
|
305
331
|
|
|
306
332
|
|
|
307
|
-
def _trace_frame(thread, frame, indent):
|
|
308
|
-
if frame.f_code is lib._TraceFunction.__call__.__code__:
|
|
333
|
+
def _trace_frame(thread: int, frame: FrameType, indent: str) -> None:
|
|
334
|
+
if frame.f_code is lib._TraceFunction.__call__.__code__: # noqa: SLF001
|
|
309
335
|
is_ctypes = True
|
|
310
|
-
func = frame.f_locals["self"]._func
|
|
336
|
+
func = frame.f_locals["self"]._func # noqa: SLF001
|
|
311
337
|
name = func.__name__
|
|
312
338
|
location = "[ctypes]"
|
|
313
339
|
else:
|
|
@@ -348,23 +374,23 @@ def _trace_frame(thread, frame, indent):
|
|
|
348
374
|
try:
|
|
349
375
|
argvalue = _trace_repr(frame.f_locals[argname])
|
|
350
376
|
print(f" {indent}{argname}={argvalue}")
|
|
351
|
-
except:
|
|
377
|
+
except: # noqa: S110, E722, PERF203
|
|
352
378
|
pass
|
|
353
379
|
|
|
354
380
|
if _trace_flush:
|
|
355
381
|
sys.stdout.flush()
|
|
356
382
|
|
|
357
383
|
|
|
358
|
-
def _thread_trace_func(thread):
|
|
359
|
-
def _trace_func(frame, event, arg):
|
|
384
|
+
def _thread_trace_func(thread: int) -> Callable[[FrameType, str, Any], object]:
|
|
385
|
+
def _trace_func(frame: FrameType, event: str, arg: Any) -> None:
|
|
360
386
|
if event == "call":
|
|
361
387
|
indent = ""
|
|
362
|
-
for
|
|
388
|
+
for _ in range(_trace_depth):
|
|
363
389
|
_trace_frame(thread, frame, indent)
|
|
364
390
|
indent += " "
|
|
365
|
-
|
|
366
|
-
if not frame:
|
|
391
|
+
if frame.f_back is None:
|
|
367
392
|
break
|
|
393
|
+
frame = frame.f_back
|
|
368
394
|
|
|
369
395
|
elif event == "exception":
|
|
370
396
|
(exception, value, traceback) = arg
|
|
@@ -373,8 +399,8 @@ def _thread_trace_func(thread):
|
|
|
373
399
|
return _trace_func
|
|
374
400
|
|
|
375
401
|
|
|
376
|
-
def _install_trace():
|
|
377
|
-
global _trace_thread_count
|
|
402
|
+
def _install_trace() -> None:
|
|
403
|
+
global _trace_thread_count # noqa: PLW0603
|
|
378
404
|
sys.setprofile(_thread_trace_func(_trace_thread_count))
|
|
379
405
|
_trace_thread_count += 1
|
|
380
406
|
|
|
@@ -385,10 +411,10 @@ def _install_trace():
|
|
|
385
411
|
class _ModuleProxy:
|
|
386
412
|
_module = None
|
|
387
413
|
|
|
388
|
-
def __init__(self, name: str):
|
|
414
|
+
def __init__(self, name: str) -> None:
|
|
389
415
|
self.__dict__["_module_name"] = name
|
|
390
416
|
|
|
391
|
-
def __getattr__(self, name):
|
|
417
|
+
def __getattr__(self, name: str): # noqa: ANN204
|
|
392
418
|
try:
|
|
393
419
|
return getattr(self._module, name)
|
|
394
420
|
except AttributeError:
|
|
@@ -402,7 +428,7 @@ class _ModuleProxy:
|
|
|
402
428
|
globals()[self._module_name] = module
|
|
403
429
|
return getattr(module, name)
|
|
404
430
|
|
|
405
|
-
def __setattr__(self, name, value):
|
|
431
|
+
def __setattr__(self, name: str, value: Any): # noqa: ANN204
|
|
406
432
|
try:
|
|
407
433
|
setattr(self._module, name, value)
|
|
408
434
|
except AttributeError:
|
pyglet/__init__.pyi
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Sequence
|
|
4
|
+
|
|
2
5
|
from . import app as app
|
|
3
6
|
from . import clock as clock
|
|
4
7
|
from . import customtypes as customtypes
|
|
@@ -26,4 +29,39 @@ MIN_PYTHON_VERSION_STR: str
|
|
|
26
29
|
compat_platform: str
|
|
27
30
|
env: str
|
|
28
31
|
value: str
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Options:
|
|
35
|
+
audio: Sequence[str]
|
|
36
|
+
debug_font: bool
|
|
37
|
+
debug_gl: bool
|
|
38
|
+
debug_gl_trace: bool
|
|
39
|
+
debug_gl_trace_args: bool
|
|
40
|
+
debug_gl_shaders: bool
|
|
41
|
+
debug_graphics_batch: bool
|
|
42
|
+
debug_lib: bool
|
|
43
|
+
debug_media: bool
|
|
44
|
+
debug_texture: bool
|
|
45
|
+
debug_trace: bool
|
|
46
|
+
debug_trace_args: bool
|
|
47
|
+
debug_trace_depth: int
|
|
48
|
+
debug_trace_flush: bool
|
|
49
|
+
debug_win32: bool
|
|
50
|
+
debug_input: bool
|
|
51
|
+
debug_x11: bool
|
|
52
|
+
shadow_window: bool
|
|
53
|
+
vsync: bool | None
|
|
54
|
+
xsync: bool
|
|
55
|
+
xlib_fullscreen_override_redirect: bool
|
|
56
|
+
search_local_libs: bool
|
|
57
|
+
win32_gdi_font: bool
|
|
58
|
+
headless: bool
|
|
59
|
+
headless_device: int
|
|
60
|
+
win32_disable_shaping: bool
|
|
61
|
+
dw_legacy_naming: bool
|
|
62
|
+
win32_disable_xinput: bool
|
|
63
|
+
com_mta: bool
|
|
64
|
+
osx_alt_loop: bool
|
|
65
|
+
shader_bind_management: bool
|
|
66
|
+
|
|
29
67
|
options: Options
|
pyglet/app/win32.py
CHANGED
pyglet/display/__init__.py
CHANGED
|
@@ -20,7 +20,7 @@ The size of a screen is determined by its current mode, which can be changed
|
|
|
20
20
|
by the application; see the documentation for :class:`Screen`.
|
|
21
21
|
|
|
22
22
|
.. versionadded:: 1.2
|
|
23
|
-
"""
|
|
23
|
+
""" # noqa: I002
|
|
24
24
|
|
|
25
25
|
import sys
|
|
26
26
|
import weakref
|
|
@@ -71,4 +71,6 @@ def get_display() -> Display:
|
|
|
71
71
|
return display
|
|
72
72
|
|
|
73
73
|
# Otherwise, create a new display and return it.
|
|
74
|
-
return Display()
|
|
74
|
+
return Display()
|
|
75
|
+
|
|
76
|
+
__all__ = ['Display', 'Screen', 'Canvas', 'ScreenMode', 'get_display']
|
pyglet/display/cocoa.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Note: The display mode API used here is Mac OS 10.6 only.
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
|
|
3
|
-
from ctypes import
|
|
4
|
-
|
|
5
|
-
from .base import Display, Screen, ScreenMode, Canvas
|
|
4
|
+
from ctypes import c_uint32, c_void_p, byref
|
|
6
5
|
|
|
6
|
+
from .base import Canvas, Display, Screen, ScreenMode
|
|
7
7
|
from pyglet.libs.darwin.cocoapy import CGDirectDisplayID, quartz, cf, ObjCClass, get_NSString
|
|
8
8
|
from pyglet.libs.darwin.cocoapy import cfstring_to_string, cfarray_to_list
|
|
9
9
|
from pyglet.libs.darwin import NSDeviceResolution
|
|
@@ -142,6 +142,6 @@ class CocoaScreenMode(ScreenMode):
|
|
|
142
142
|
class CocoaCanvas(Canvas):
|
|
143
143
|
|
|
144
144
|
def __init__(self, display, screen, nsview):
|
|
145
|
-
super(
|
|
145
|
+
super().__init__(display)
|
|
146
146
|
self.screen = screen
|
|
147
147
|
self.nsview = nsview
|
pyglet/display/headless.py
CHANGED
pyglet/display/win32.py
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
from .base import Display, Screen, ScreenMode, Canvas
|
|
2
2
|
|
|
3
3
|
from pyglet.libs.win32 import _user32, _shcore, _gdi32
|
|
4
|
-
from pyglet.libs.win32.constants import
|
|
5
|
-
|
|
4
|
+
from pyglet.libs.win32.constants import (
|
|
5
|
+
CDS_FULLSCREEN,
|
|
6
|
+
DISP_CHANGE_SUCCESSFUL,
|
|
7
|
+
ENUM_CURRENT_SETTINGS,
|
|
8
|
+
WINDOWS_8_1_OR_GREATER,
|
|
9
|
+
WINDOWS_VISTA_OR_GREATER,
|
|
10
|
+
WINDOWS_10_CREATORS_UPDATE_OR_GREATER,
|
|
11
|
+
USER_DEFAULT_SCREEN_DPI,
|
|
12
|
+
LOGPIXELSX,
|
|
13
|
+
LOGPIXELSY,
|
|
14
|
+
|
|
15
|
+
)
|
|
16
|
+
from pyglet.libs.win32.types import (
|
|
17
|
+
DEVMODE,
|
|
18
|
+
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
|
19
|
+
MONITORINFOEX,
|
|
20
|
+
MONITORENUMPROC,
|
|
21
|
+
PROCESS_PER_MONITOR_DPI_AWARE,
|
|
22
|
+
UINT,
|
|
23
|
+
sizeof,
|
|
24
|
+
byref
|
|
25
|
+
)
|
|
6
26
|
from pyglet.libs.win32.context_managers import device_context
|
|
7
27
|
|
|
8
28
|
|
pyglet/display/xlib.py
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import ctypes
|
|
2
|
-
from ctypes import
|
|
4
|
+
from ctypes import c_int, c_char_p, c_buffer, POINTER, byref, cast
|
|
3
5
|
|
|
4
6
|
from pyglet.util import asbytes
|
|
5
7
|
|
|
6
8
|
from pyglet import app
|
|
7
9
|
from pyglet.app.xlib import XlibSelectDevice
|
|
8
|
-
from .base import Display, Screen, ScreenMode, Canvas
|
|
9
10
|
|
|
11
|
+
from ..libs.x11.xlib import Window
|
|
10
12
|
from . import xlib_vidmoderestore
|
|
13
|
+
from .base import Canvas, Display, Screen, ScreenMode
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
# XXX
|
|
@@ -176,7 +179,7 @@ class XlibScreen(Screen):
|
|
|
176
179
|
_initial_mode = None
|
|
177
180
|
|
|
178
181
|
def __init__(self, display, x, y, width, height, xinerama):
|
|
179
|
-
super(
|
|
182
|
+
super().__init__(display, x, y, width, height)
|
|
180
183
|
self._xinerama = xinerama
|
|
181
184
|
|
|
182
185
|
def get_dpi(self):
|
|
@@ -252,7 +255,7 @@ class XlibScreen(Screen):
|
|
|
252
255
|
|
|
253
256
|
xf86vmode.XF86VidModeSwitchToMode(self.display._display, self.display.x_screen, mode.info)
|
|
254
257
|
xlib.XFlush(self.display._display)
|
|
255
|
-
xf86vmode.XF86VidModeSetViewPort(self.display._display,
|
|
258
|
+
xf86vmode.XF86VidModeSetViewPort(self.display._display, self.display.x_screen, 0, 0)
|
|
256
259
|
xlib.XFlush(self.display._display)
|
|
257
260
|
|
|
258
261
|
self.width = mode.width
|
|
@@ -267,10 +270,9 @@ class XlibScreen(Screen):
|
|
|
267
270
|
f"width={self.width}, height={self.height}, xinerama={self._xinerama})"
|
|
268
271
|
|
|
269
272
|
|
|
270
|
-
|
|
271
273
|
class XlibScreenMode(ScreenMode):
|
|
272
274
|
def __init__(self, screen, info):
|
|
273
|
-
super(
|
|
275
|
+
super().__init__(screen)
|
|
274
276
|
self.info = info
|
|
275
277
|
self.width = info.hdisplay
|
|
276
278
|
self.height = info.vdisplay
|
|
@@ -278,7 +280,9 @@ class XlibScreenMode(ScreenMode):
|
|
|
278
280
|
self.depth = None
|
|
279
281
|
|
|
280
282
|
|
|
281
|
-
class XlibCanvas(Canvas):
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
class XlibCanvas(Canvas): # noqa: D101
|
|
284
|
+
display: XlibDisplay
|
|
285
|
+
|
|
286
|
+
def __init__(self, display: XlibDisplay, x_window: Window) -> None: # noqa: D107
|
|
287
|
+
super().__init__(display)
|
|
284
288
|
self.x_window = x_window
|
pyglet/event.py
CHANGED
|
@@ -126,14 +126,19 @@ from typing import TYPE_CHECKING
|
|
|
126
126
|
from weakref import WeakMethod
|
|
127
127
|
|
|
128
128
|
if TYPE_CHECKING:
|
|
129
|
+
import sys
|
|
129
130
|
from typing import Any, Callable, Generator
|
|
131
|
+
if sys.version_info >= (3, 11):
|
|
132
|
+
from typing import Self
|
|
133
|
+
else:
|
|
134
|
+
Self = Any
|
|
130
135
|
|
|
131
136
|
|
|
132
137
|
EVENT_HANDLED = True
|
|
133
138
|
EVENT_UNHANDLED = None
|
|
134
139
|
|
|
135
140
|
|
|
136
|
-
class EventException(Exception):
|
|
141
|
+
class EventException(Exception): # noqa: N818
|
|
137
142
|
"""An exception raised when an event handler could not be attached."""
|
|
138
143
|
|
|
139
144
|
|
|
@@ -147,7 +152,7 @@ class EventDispatcher:
|
|
|
147
152
|
_event_stack: tuple | list = ()
|
|
148
153
|
|
|
149
154
|
@classmethod
|
|
150
|
-
def register_event_type(cls:
|
|
155
|
+
def register_event_type(cls: type[Self], name: str) -> str:
|
|
151
156
|
"""Register an event type with the dispatcher.
|
|
152
157
|
|
|
153
158
|
Before dispatching events, they must first be registered by name.
|
|
@@ -179,14 +184,15 @@ class EventDispatcher:
|
|
|
179
184
|
self._event_stack.insert(0, {})
|
|
180
185
|
self.set_handlers(*args, **kwargs)
|
|
181
186
|
|
|
182
|
-
def _get_handlers(self, args:
|
|
187
|
+
def _get_handlers(self, args: list, kwargs: dict) -> Generator[str, Callable]:
|
|
183
188
|
"""Implement handler matching on arguments for set_handlers and remove_handlers."""
|
|
184
189
|
for obj in args:
|
|
185
190
|
if inspect.isroutine(obj):
|
|
186
191
|
# Single magically named function
|
|
187
192
|
name = obj.__name__
|
|
188
193
|
if name not in self.event_types:
|
|
189
|
-
|
|
194
|
+
msg = f'Unknown event "{name}"'
|
|
195
|
+
raise EventException(msg)
|
|
190
196
|
if inspect.ismethod(obj):
|
|
191
197
|
yield name, WeakMethod(obj, partial(self._remove_handler, name))
|
|
192
198
|
else:
|
|
@@ -201,7 +207,8 @@ class EventDispatcher:
|
|
|
201
207
|
for name, handler in kwargs.items():
|
|
202
208
|
# Function for handling given event (no magic)
|
|
203
209
|
if name not in self.event_types:
|
|
204
|
-
|
|
210
|
+
msg = f'Unknown event "{name}"'
|
|
211
|
+
raise EventException(msg)
|
|
205
212
|
if inspect.ismethod(handler):
|
|
206
213
|
yield name, WeakMethod(handler, partial(self._remove_handler, name))
|
|
207
214
|
else:
|
|
@@ -230,7 +237,7 @@ class EventDispatcher:
|
|
|
230
237
|
|
|
231
238
|
def pop_handlers(self) -> None:
|
|
232
239
|
"""Pop the top level of event handlers off the stack."""
|
|
233
|
-
assert self._event_stack
|
|
240
|
+
assert self._event_stack, 'No handlers pushed'
|
|
234
241
|
|
|
235
242
|
del self._event_stack[0]
|
|
236
243
|
|
|
@@ -251,13 +258,14 @@ class EventDispatcher:
|
|
|
251
258
|
handlers = list(self._get_handlers(args, kwargs))
|
|
252
259
|
|
|
253
260
|
# Find the first stack frame containing any of the handlers
|
|
254
|
-
def find_frame():
|
|
261
|
+
def find_frame() -> dict | None:
|
|
255
262
|
for _frame in self._event_stack:
|
|
256
263
|
for _name, _handler in handlers:
|
|
257
264
|
if _name not in _frame:
|
|
258
265
|
continue
|
|
259
266
|
if _frame[_name] == _handler:
|
|
260
267
|
return _frame
|
|
268
|
+
return None
|
|
261
269
|
|
|
262
270
|
frame = find_frame()
|
|
263
271
|
|
|
@@ -270,7 +278,7 @@ class EventDispatcher:
|
|
|
270
278
|
try:
|
|
271
279
|
if frame[name] == handler:
|
|
272
280
|
del frame[name]
|
|
273
|
-
except KeyError:
|
|
281
|
+
except KeyError: # noqa: PERF203
|
|
274
282
|
pass
|
|
275
283
|
|
|
276
284
|
# Remove the frame if it's empty.
|
|
@@ -302,7 +310,6 @@ class EventDispatcher:
|
|
|
302
310
|
This is normally called from a dead ``WeakMethod`` to remove itself from the
|
|
303
311
|
event stack.
|
|
304
312
|
"""
|
|
305
|
-
|
|
306
313
|
# Iterate over a copy as we might mutate the list
|
|
307
314
|
for frame in list(self._event_stack):
|
|
308
315
|
|
|
@@ -428,13 +435,13 @@ class EventDispatcher:
|
|
|
428
435
|
|
|
429
436
|
raise exception
|
|
430
437
|
|
|
431
|
-
def _dump_handlers(self):
|
|
438
|
+
def _dump_handlers(self) -> None:
|
|
432
439
|
|
|
433
440
|
for level, handlers in enumerate(self._event_stack):
|
|
434
|
-
print(f"level: {level}")
|
|
441
|
+
print(f"level: {level}")
|
|
435
442
|
|
|
436
443
|
for event_type, handler in handlers.items():
|
|
437
|
-
print(f" - '{event_type}': {handler}")
|
|
444
|
+
print(f" - '{event_type}': {handler}")
|
|
438
445
|
|
|
439
446
|
# Decorator
|
|
440
447
|
|
|
@@ -488,5 +495,5 @@ class EventDispatcher:
|
|
|
488
495
|
|
|
489
496
|
return decorator
|
|
490
497
|
|
|
491
|
-
|
|
492
|
-
|
|
498
|
+
msg = "Argument must be the name of the event as a `str`."
|
|
499
|
+
raise TypeError(msg)
|