pyglet 3.0.dev10__py3-none-any.whl → 3.0.dev12__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 (257) hide show
  1. pyglet/__init__.py +4 -2
  2. pyglet/__init__.pyi +6 -1
  3. pyglet/app/__init__.py +13 -5
  4. pyglet/app/async_app.py +80 -17
  5. pyglet/app/base.py +31 -15
  6. pyglet/app/cocoa.py +12 -8
  7. pyglet/app/ios.py +9 -4
  8. pyglet/clock.py +1 -1
  9. pyglet/config/__init__.py +26 -9
  10. pyglet/config/base.py +9 -4
  11. pyglet/config/gl/__init__.py +31 -17
  12. pyglet/config/gl/egl.py +5 -6
  13. pyglet/config/gl/ios.py +16 -6
  14. pyglet/config/gl/macos.py +62 -47
  15. pyglet/config/gl/webgl.py +7 -5
  16. pyglet/config/gl/windows.py +16 -17
  17. pyglet/config/gl/x11.py +4 -4
  18. pyglet/customtypes.py +12 -1
  19. pyglet/display/__init__.py +2 -3
  20. pyglet/display/base.py +33 -19
  21. pyglet/display/cocoa.py +8 -4
  22. pyglet/display/emscripten.py +14 -4
  23. pyglet/display/headless.py +9 -3
  24. pyglet/display/ios.py +6 -2
  25. pyglet/display/wayland.py +29 -14
  26. pyglet/display/win32.py +115 -21
  27. pyglet/display/xlib.py +99 -20
  28. pyglet/display/xlib_vidmoderestore.py +1 -1
  29. pyglet/enums.py +25 -0
  30. pyglet/event.py +13 -12
  31. pyglet/experimental/geoshader_sprite.py +4 -7
  32. pyglet/experimental/particles.py +3 -5
  33. pyglet/font/__init__.py +13 -14
  34. pyglet/font/base.py +90 -33
  35. pyglet/font/dwrite/__init__.py +68 -40
  36. pyglet/font/fontconfig.py +21 -19
  37. pyglet/font/freetype.py +58 -81
  38. pyglet/font/freetype_lib.py +1 -1
  39. pyglet/font/group.py +18 -4
  40. pyglet/font/harfbuzz.py +20 -15
  41. pyglet/font/pyodide_js.py +40 -16
  42. pyglet/font/quartz.py +23 -15
  43. pyglet/font/ttf.py +28 -16
  44. pyglet/font/user.py +33 -17
  45. pyglet/font/win32.py +10 -5
  46. pyglet/graphics/__init__.py +23 -4
  47. pyglet/graphics/allocation.py +124 -3
  48. pyglet/graphics/api/__init__.py +12 -14
  49. pyglet/graphics/api/base.py +67 -15
  50. pyglet/graphics/api/gl/__init__.py +1 -3
  51. pyglet/graphics/api/gl/buffer.py +10 -8
  52. pyglet/graphics/api/gl/cocoa/context.py +3 -2
  53. pyglet/graphics/api/gl/context.py +54 -10
  54. pyglet/graphics/api/gl/draw.py +51 -3
  55. pyglet/graphics/api/gl/egl/context.py +5 -3
  56. pyglet/graphics/api/gl/enums.py +37 -2
  57. pyglet/graphics/api/gl/external.py +210 -0
  58. pyglet/graphics/api/gl/framebuffer.py +178 -27
  59. pyglet/graphics/api/gl/gl.py +3 -2
  60. pyglet/graphics/api/gl/gl_fallback.py +3 -0
  61. pyglet/graphics/api/gl/gl_info.py +15 -4
  62. pyglet/graphics/api/gl/global_opengl.py +28 -26
  63. pyglet/graphics/api/gl/ios/context.py +36 -11
  64. pyglet/graphics/api/gl/lib.py +26 -10
  65. pyglet/graphics/api/gl/renderer.py +5 -1
  66. pyglet/graphics/api/gl/shader.py +69 -95
  67. pyglet/graphics/api/gl/shapes.py +2 -1
  68. pyglet/graphics/api/gl/sprite.py +4 -5
  69. pyglet/graphics/api/gl/state.py +195 -20
  70. pyglet/graphics/api/gl/text.py +169 -42
  71. pyglet/graphics/api/gl/texture.py +221 -273
  72. pyglet/graphics/api/gl/vertexarray.py +6 -7
  73. pyglet/graphics/api/gl/vertexdomain.py +207 -104
  74. pyglet/graphics/api/gl/win32/context.py +11 -12
  75. pyglet/graphics/api/gl/win32/wgl_info.py +1 -0
  76. pyglet/graphics/api/gl/xlib/context.py +7 -6
  77. pyglet/graphics/api/gl/xlib/glx_info.py +2 -2
  78. pyglet/graphics/api/gl2/buffer.py +3 -3
  79. pyglet/graphics/api/gl2/draw.py +21 -4
  80. pyglet/graphics/api/gl2/framebuffer.py +7 -2
  81. pyglet/graphics/api/gl2/global_opengl.py +9 -12
  82. pyglet/graphics/api/gl2/shader.py +13 -14
  83. pyglet/graphics/api/gl2/shapes.py +2 -1
  84. pyglet/graphics/api/gl2/sprite.py +3 -4
  85. pyglet/graphics/api/gl2/state.py +6 -1
  86. pyglet/graphics/api/gl2/text.py +155 -41
  87. pyglet/graphics/api/gl2/vertexdomain.py +117 -66
  88. pyglet/graphics/api/webgl/__init__.py +24 -22
  89. pyglet/graphics/api/webgl/buffer.py +20 -19
  90. pyglet/graphics/api/webgl/context.py +14 -7
  91. pyglet/graphics/api/webgl/draw.py +50 -4
  92. pyglet/graphics/api/webgl/enums.py +37 -2
  93. pyglet/graphics/api/webgl/framebuffer.py +37 -12
  94. pyglet/graphics/api/webgl/gl.py +2 -9
  95. pyglet/graphics/api/webgl/renderer.py +8 -2
  96. pyglet/graphics/api/webgl/shader.py +84 -106
  97. pyglet/graphics/api/webgl/shapes.py +2 -1
  98. pyglet/graphics/api/webgl/sprite.py +4 -5
  99. pyglet/graphics/api/webgl/state.py +193 -14
  100. pyglet/graphics/api/webgl/text.py +99 -89
  101. pyglet/graphics/api/webgl/texture.py +186 -261
  102. pyglet/graphics/api/webgl/vertexarray.py +5 -8
  103. pyglet/graphics/api/webgl/vertexdomain.py +191 -107
  104. pyglet/graphics/api/webgl/webgl_js.pyi +7 -0
  105. pyglet/graphics/atlas.py +13 -8
  106. pyglet/graphics/attributes.py +224 -0
  107. pyglet/graphics/buffer.py +25 -7
  108. pyglet/graphics/draw.py +905 -213
  109. pyglet/graphics/framebuffer.py +170 -7
  110. pyglet/graphics/instance.py +10 -5
  111. pyglet/graphics/resource.py +2 -2
  112. pyglet/graphics/shader.py +234 -362
  113. pyglet/graphics/state.py +59 -21
  114. pyglet/graphics/texture.py +417 -223
  115. pyglet/graphics/vertexdomain.py +301 -365
  116. pyglet/graphics/vertexstorage.py +612 -0
  117. pyglet/gui/__init__.py +6 -2
  118. pyglet/gui/layout.py +809 -0
  119. pyglet/gui/manager.py +373 -0
  120. pyglet/gui/ninepatch.py +68 -41
  121. pyglet/gui/styles.py +130 -0
  122. pyglet/gui/widgets.py +630 -118
  123. pyglet/image/__init__.py +3 -1
  124. pyglet/image/animation.py +8 -3
  125. pyglet/image/base.py +238 -68
  126. pyglet/image/codecs/__init__.py +16 -4
  127. pyglet/image/codecs/dds.py +3 -1
  128. pyglet/image/codecs/gdkpixbuf2.py +2 -3
  129. pyglet/image/codecs/{quartz.py → imageio.py} +29 -26
  130. pyglet/image/codecs/ktx2.py +3 -1
  131. pyglet/image/codecs/qoi.py +275 -0
  132. pyglet/image/codecs/wic.py +7 -6
  133. pyglet/info.py +34 -35
  134. pyglet/input/__init__.py +7 -4
  135. pyglet/input/base.py +61 -48
  136. pyglet/input/emscripten/gamepad_js.py +82 -52
  137. pyglet/input/linux/__init__.py +1 -1
  138. pyglet/input/linux/evdev.py +111 -62
  139. pyglet/input/macos/darwin_gc.py +151 -42
  140. pyglet/input/macos/darwin_hid.py +3 -1
  141. pyglet/input/win32/__init__.py +32 -25
  142. pyglet/input/win32/directinput.py +74 -46
  143. pyglet/input/win32/xinput.py +56 -33
  144. pyglet/lib.py +8 -8
  145. pyglet/libs/darwin/cocoapy/__init__.py +2 -1
  146. pyglet/libs/darwin/cocoapy/lib_appkit.py +1 -0
  147. pyglet/libs/darwin/cocoapy/lib_corefoundation.py +3 -3
  148. pyglet/libs/darwin/cocoapy/lib_uikit.py +5 -0
  149. pyglet/libs/darwin/cocoapy/runtime.py +5 -7
  150. pyglet/libs/emscripten/__init__.py +12 -1
  151. pyglet/libs/emscripten/dom.pyi +173 -0
  152. pyglet/libs/emscripten/filesystem.py +3 -3
  153. pyglet/libs/emscripten/pyglet_emscripten.js +58 -10
  154. pyglet/libs/linux/wayland/client.py +4 -0
  155. pyglet/libs/linux/x11/xcursor.py +71 -0
  156. pyglet/libs/linux/x11/xf86vmode.py +2 -9
  157. pyglet/libs/linux/x11/xinerama.py +2 -9
  158. pyglet/libs/linux/x11/xinput.py +2 -9
  159. pyglet/libs/linux/x11/xinput2.py +222 -0
  160. pyglet/libs/linux/x11/xlib.py +2 -9
  161. pyglet/libs/linux/x11/xrandr.py +166 -0
  162. pyglet/libs/linux/x11/xsync.py +2 -9
  163. pyglet/libs/win32/__init__.py +14 -0
  164. pyglet/libs/win32/com.py +27 -12
  165. pyglet/libs/win32/constants.py +53 -0
  166. pyglet/libs/win32/types.py +56 -11
  167. pyglet/math.py +19 -16
  168. pyglet/media/__init__.py +18 -12
  169. pyglet/media/codecs/__init__.py +1 -3
  170. pyglet/media/codecs/base.py +268 -69
  171. pyglet/media/codecs/coreaudio.py +7 -3
  172. pyglet/media/codecs/ffmpeg.py +116 -48
  173. pyglet/media/codecs/ffmpeg_lib/compat.py +2 -1
  174. pyglet/media/codecs/ffmpeg_lib/libavcodec.py +8 -7
  175. pyglet/media/codecs/ffmpeg_lib/libavformat.py +7 -3
  176. pyglet/media/codecs/ffmpeg_lib/libavutil.py +11 -2
  177. pyglet/media/codecs/ffmpeg_lib/libswresample.py +2 -2
  178. pyglet/media/codecs/ffmpeg_lib/libswscale.py +2 -2
  179. pyglet/media/codecs/gstreamer.py +52 -24
  180. pyglet/media/codecs/pyogg.py +139 -69
  181. pyglet/media/codecs/wave.py +7 -3
  182. pyglet/media/codecs/webaudio_pyodide.py +19 -8
  183. pyglet/media/codecs/wmf.py +33 -5
  184. pyglet/media/devices/win32.py +42 -27
  185. pyglet/media/drivers/__init__.py +1 -3
  186. pyglet/media/drivers/base.py +89 -3
  187. pyglet/media/drivers/directsound/adaptation.py +76 -1
  188. pyglet/media/drivers/listener.py +4 -4
  189. pyglet/media/drivers/openal/adaptation.py +105 -17
  190. pyglet/media/drivers/openal/interface.py +107 -70
  191. pyglet/media/drivers/openal/lib_alc.py +2 -9
  192. pyglet/media/drivers/openal/lib_openal.py +2 -9
  193. pyglet/media/drivers/pulse/adaptation.py +98 -16
  194. pyglet/media/drivers/pulse/interface.py +43 -33
  195. pyglet/media/drivers/pulse/lib_pulseaudio.py +2 -9
  196. pyglet/media/drivers/pyodide_js/adaptation.py +4 -6
  197. pyglet/media/drivers/silent/adaptation.py +63 -1
  198. pyglet/media/drivers/xaudio2/adaptation.py +123 -3
  199. pyglet/media/drivers/xaudio2/interface.py +53 -20
  200. pyglet/media/player.py +215 -23
  201. pyglet/media/synthesis.py +64 -44
  202. pyglet/model/__init__.py +5 -7
  203. pyglet/model/codecs/base.py +7 -7
  204. pyglet/model/codecs/gltf.py +1 -1
  205. pyglet/model/codecs/obj.py +8 -7
  206. pyglet/resource.py +148 -40
  207. pyglet/shapes.py +103 -38
  208. pyglet/sprite.py +42 -41
  209. pyglet/storage.py +4 -2
  210. pyglet/text/__init__.py +58 -24
  211. pyglet/text/caret.py +43 -6
  212. pyglet/text/document.py +12 -6
  213. pyglet/text/formats/attributed.py +3 -2
  214. pyglet/text/formats/html.py +18 -14
  215. pyglet/text/formats/structured.py +70 -34
  216. pyglet/text/layout/__init__.py +2 -2
  217. pyglet/text/layout/base.py +271 -99
  218. pyglet/text/layout/boxes.py +226 -73
  219. pyglet/text/layout/flow.py +151 -71
  220. pyglet/text/layout/incremental.py +129 -42
  221. pyglet/text/layout/scrolling.py +41 -26
  222. pyglet/text/runlist.py +8 -7
  223. pyglet/util.py +21 -6
  224. pyglet/window/__init__.py +266 -62
  225. pyglet/window/apple/cocoa/__init__.py +76 -49
  226. pyglet/window/apple/cocoa/pyglet_delegate.py +17 -19
  227. pyglet/window/apple/cocoa/pyglet_textview.py +16 -18
  228. pyglet/window/apple/cocoa/pyglet_view.py +213 -88
  229. pyglet/window/apple/cocoa/pyglet_window.py +5 -11
  230. pyglet/window/apple/{ios.py → ios/__init__.py} +90 -10
  231. pyglet/window/apple/ios/textinput.py +240 -0
  232. pyglet/window/camera/__init__.py +2 -0
  233. pyglet/window/camera/base.py +194 -56
  234. pyglet/window/camera/camera2d.py +112 -27
  235. pyglet/window/camera/camera3d.py +37 -10
  236. pyglet/window/dialog/__init__.py +3 -4
  237. pyglet/window/dialog/base.py +0 -1
  238. pyglet/window/dialog/darwin.py +1 -1
  239. pyglet/window/dialog/linux.py +5 -1
  240. pyglet/window/dialog/windows.py +2 -3
  241. pyglet/window/emscripten/__init__.py +307 -101
  242. pyglet/window/emscripten/textinput.py +199 -0
  243. pyglet/window/event.py +17 -2
  244. pyglet/window/headless/__init__.py +18 -7
  245. pyglet/window/key.py +3 -0
  246. pyglet/window/pen.py +110 -0
  247. pyglet/window/textinput.py +195 -0
  248. pyglet/window/wayland/__init__.py +86 -10
  249. pyglet/window/win32/__init__.py +127 -36
  250. pyglet/window/xlib/__init__.py +315 -88
  251. pyglet/window/xlib/pen.py +71 -0
  252. {pyglet-3.0.dev10.dist-info → pyglet-3.0.dev12.dist-info}/METADATA +5 -5
  253. pyglet-3.0.dev12.dist-info/RECORD +387 -0
  254. {pyglet-3.0.dev10.dist-info → pyglet-3.0.dev12.dist-info}/WHEEL +1 -1
  255. pyglet/gui/frame.py +0 -205
  256. pyglet-3.0.dev10.dist-info/RECORD +0 -373
  257. {pyglet-3.0.dev10.dist-info → pyglet-3.0.dev12.dist-info}/licenses/LICENSE +0 -0
pyglet/__init__.py CHANGED
@@ -8,6 +8,8 @@ import os
8
8
  import sys
9
9
  import warnings
10
10
 
11
+ IS_DOC_BUILD = os.environ.get("PYGLET_DOC_BUILD") == "1"
12
+
11
13
  from dataclasses import dataclass, field
12
14
  from typing import TYPE_CHECKING, Literal, Sequence
13
15
 
@@ -18,10 +20,10 @@ if TYPE_CHECKING:
18
20
  from typing import Any, Callable, ItemsView, Sized
19
21
 
20
22
  #: The release version
21
- version = '3.0.dev10'
23
+ version = '3.0.dev12'
22
24
  __version__ = version
23
25
 
24
- MIN_PYTHON_VERSION = 3, 10
26
+ MIN_PYTHON_VERSION = 3, 11
25
27
  MIN_PYTHON_VERSION_STR = ".".join([str(v) for v in MIN_PYTHON_VERSION])
26
28
 
27
29
  #: The Pyodide release used to develop and test pyglet's browser support.
pyglet/__init__.pyi CHANGED
@@ -1,6 +1,9 @@
1
1
 
2
2
  from dataclasses import dataclass
3
3
  from typing import Any, ItemsView, Literal, Sequence
4
+ from .enums import GraphicsAPI
5
+
6
+ def _install_trace() -> None: ...
4
7
 
5
8
  from . import app as app
6
9
  from . import clock as clock
@@ -24,6 +27,8 @@ from . import sprite as sprite
24
27
  from . import text as text
25
28
  from . import window as window
26
29
 
30
+ IS_DOC_BUILD: bool
31
+
27
32
  version: str
28
33
  MIN_PYTHON_VERSION: tuple[int, int]
29
34
  MIN_PYTHON_VERSION_STR: str
@@ -72,7 +77,7 @@ class Options:
72
77
  dpi_scaling: Literal["platform", "stretch"]
73
78
  shader_bind_management: bool
74
79
  wayland: bool
75
- backend: str | None
80
+ backend: Literal['opengl', 'gl2', 'gles3', 'gles2', 'webgl'] | GraphicsAPI
76
81
  optimize_states: bool
77
82
  opengl_persistent_buffers: bool
78
83
  pyodide: PyodideOptions
pyglet/app/__init__.py CHANGED
@@ -31,17 +31,21 @@ default policy is to wait until all windows are closed)::
31
31
 
32
32
  from __future__ import annotations
33
33
 
34
- import platform
35
34
  import sys
35
+ import signal
36
+ import platform
36
37
  import weakref
38
+ from typing import TYPE_CHECKING
39
+
40
+ if TYPE_CHECKING:
41
+ from pyglet.window import BaseWindow
37
42
 
38
43
  import pyglet
39
44
  from pyglet import compat_platform
40
45
  from pyglet.app.base import EventLoop
41
46
 
42
- _is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run
43
47
 
44
- if _is_pyglet_doc_run:
48
+ if TYPE_CHECKING or pyglet.IS_DOC_BUILD:
45
49
  from pyglet.app.base import PlatformEventLoop
46
50
  else:
47
51
  if compat_platform == 'darwin':
@@ -67,7 +71,7 @@ class AppException(Exception):
67
71
  pass
68
72
 
69
73
 
70
- windows = weakref.WeakSet()
74
+ windows: weakref.WeakSet[BaseWindow] = weakref.WeakSet()
71
75
  """Set of all open windows (including invisible windows). Instances of
72
76
  :class:`pyglet.window.Window` are automatically added to this set upon
73
77
  construction. The set uses weak references, so windows are removed from
@@ -82,7 +86,11 @@ def run(interval: float | None = 1 / 60) -> None:
82
86
 
83
87
  pyglet.app.event_loop.run(interval)
84
88
 
89
+ ..note: This convenience function also adds a SIGINT signal handler
90
+ to automatically call ``pyglet.app.exit`` when this signal
91
+ is detected.
85
92
  """
93
+ signal.signal(signal.SIGINT, lambda _s, _f: event_loop.exit())
86
94
  event_loop.run(interval)
87
95
 
88
96
 
@@ -108,4 +116,4 @@ event_loop = EventLoop()
108
116
 
109
117
  #: The platform-dependent event loop. Applications are strongly discouraged
110
118
  #: from subclassing or replacing this :class:`PlatformEventLoop` object.
111
- platform_event_loop = PlatformEventLoop()
119
+ platform_event_loop: PlatformEventLoop = PlatformEventLoop()
pyglet/app/async_app.py CHANGED
@@ -4,13 +4,15 @@ from __future__ import annotations
4
4
  import asyncio
5
5
  from contextlib import suppress
6
6
  import sys
7
- from typing import Any, Callable, TYPE_CHECKING
7
+ from typing import Any, TYPE_CHECKING
8
8
 
9
9
  import pyglet
10
10
  from pyglet import app, clock, event
11
11
  from pyglet.app.base import ClockWindowDrawSource, WindowDrawSource
12
12
 
13
13
  if TYPE_CHECKING:
14
+ from collections.abc import Callable
15
+
14
16
  from pyglet.event import EventDispatcher
15
17
  from pyglet.window import BaseWindow
16
18
 
@@ -22,13 +24,57 @@ except ImportError as exc:
22
24
  from pyglet.libs.emscripten.proxies import ProxyRegistry
23
25
 
24
26
 
27
+ class _AsyncClock(clock.Clock):
28
+ """Wake the event loop when new clock work is due sooner."""
29
+
30
+ def __init__(self, time_function: Callable[[], float], on_schedule: Callable[[float | None], None]) -> None:
31
+ """Bind the clock to the event loop's deadline notification callback."""
32
+ super().__init__(time_function)
33
+ self._schedule_callback = on_schedule
34
+
35
+ def _next_interval_deadline(self) -> float:
36
+ if self._schedule_interval_items:
37
+ return self._schedule_interval_items[0].next_ts
38
+ return float('inf')
39
+
40
+ def _notify_earlier_deadline(self, previous_deadline: float) -> None:
41
+ deadline = self._next_interval_deadline()
42
+ if deadline < previous_deadline:
43
+ self._schedule_callback(deadline)
44
+
45
+ def schedule(self, func: Callable, *args: Any, **kwargs: Any) -> None:
46
+ super().schedule(func, *args, **kwargs)
47
+ self._schedule_callback(None)
48
+
49
+ def schedule_once(self, func: Callable, delay: float, *args: Any, **kwargs: Any) -> None:
50
+ previous_deadline = self._next_interval_deadline()
51
+ super().schedule_once(func, delay, *args, **kwargs)
52
+ self._notify_earlier_deadline(previous_deadline)
53
+
54
+ def schedule_interval(self, func: Callable, interval: float, *args: Any, **kwargs: Any) -> None:
55
+ previous_deadline = self._next_interval_deadline()
56
+ super().schedule_interval(func, interval, *args, **kwargs)
57
+ self._notify_earlier_deadline(previous_deadline)
58
+
59
+ def schedule_interval_fixed_delay(self, func: Callable, interval: float, *args: Any, **kwargs: Any) -> None:
60
+ previous_deadline = self._next_interval_deadline()
61
+ super().schedule_interval_fixed_delay(func, interval, *args, **kwargs)
62
+ self._notify_earlier_deadline(previous_deadline)
63
+
64
+ def schedule_interval_soft(self, func: Callable, interval: float, *args: Any, **kwargs: Any) -> None:
65
+ previous_deadline = self._next_interval_deadline()
66
+ super().schedule_interval_soft(func, interval, *args, **kwargs)
67
+ self._notify_earlier_deadline(previous_deadline)
68
+
69
+
25
70
  class AsyncEventLoop(event.EventDispatcher):
26
71
  """Browser event loop which preserves pyglet's normal clock semantics."""
27
72
 
28
73
  def __init__(self) -> None:
29
74
  """Create the browser clock and callback state."""
30
75
  self._has_exit_event = asyncio.Event()
31
- self.clock = clock.Clock(self._get_js_time)
76
+ self._clock_wait_deadline: float | None = None
77
+ self.clock = _AsyncClock(self._get_js_time, self._on_clock_schedule)
32
78
  pyglet.clock.set_default(self.clock)
33
79
  self.is_running = False
34
80
  self._interval: float | None = None
@@ -36,6 +82,12 @@ class AsyncEventLoop(event.EventDispatcher):
36
82
  self._window_draw_source: WindowDrawSource | None = None
37
83
  self._window_draw_exception: tuple[type[BaseException], BaseException, Any] | None = None
38
84
 
85
+ def _on_clock_schedule(self, next_ts: float | None) -> None:
86
+ """Wake the loop if new work is due before its next wakeup."""
87
+ deadline = self._clock_wait_deadline
88
+ if deadline is not None and (next_ts is None or next_ts < deadline):
89
+ app.platform_event_loop.notify()
90
+
39
91
  def _get_js_time(self) -> float:
40
92
  """Use Javascripts performance.now for the clock accuracy.
41
93
 
@@ -51,7 +103,7 @@ class AsyncEventLoop(event.EventDispatcher):
51
103
  try:
52
104
  window.draw(dt)
53
105
  except BaseException: # noqa: BLE001 - JS callbacks cannot unwind Python exceptions.
54
- self._window_draw_exception = sys.exc_info()
106
+ self._window_draw_exception = sys.exc_info() # type: ignore[assignment]
55
107
  self.exit()
56
108
 
57
109
  def _raise_window_draw_exception(self) -> None:
@@ -104,6 +156,7 @@ class AsyncEventLoop(event.EventDispatcher):
104
156
  self._interval = interval
105
157
  self._has_exit_event.clear()
106
158
  platform_event_loop = app.platform_event_loop
159
+ assert isinstance(platform_event_loop, AsyncPlatformEventLoop)
107
160
 
108
161
  try:
109
162
  platform_event_loop.start()
@@ -113,7 +166,16 @@ class AsyncEventLoop(event.EventDispatcher):
113
166
 
114
167
  while not self._has_exit_event.is_set():
115
168
  timeout = self.idle()
116
- await platform_event_loop.wait(timeout)
169
+ # idle finishes before wait() yields, so its timeout includes
170
+ # any work scheduled during that clock tick.
171
+ if timeout is None:
172
+ self._clock_wait_deadline = float('inf')
173
+ elif timeout > 0:
174
+ self._clock_wait_deadline = self.clock.time() + timeout
175
+ try:
176
+ await platform_event_loop.wait(timeout)
177
+ finally:
178
+ self._clock_wait_deadline = None
117
179
  platform_event_loop.dispatch_posted_events()
118
180
  finally:
119
181
  self.is_running = False
@@ -195,20 +257,21 @@ class AsyncPlatformEventLoop:
195
257
  self._is_running = True
196
258
 
197
259
  async def wait(self, timeout: float | None) -> None:
198
- if not self._event_queue.empty():
199
- return
200
-
260
+ # Keep a wakeup sent before the wait(), so the loop can process it now.
261
+ if self._event_queue.empty() and not self._wake_event.is_set():
262
+ if timeout is None:
263
+ await self._wake_event.wait()
264
+ elif timeout > 0:
265
+ # wait_for() uses call_at(); Pyodide rejects its deadline if
266
+ # it expires during timer setup. Keep the delay relative.
267
+ timer = asyncio.get_running_loop().call_later(timeout, self._wake_event.set)
268
+ try:
269
+ await self._wake_event.wait()
270
+ finally:
271
+ timer.cancel()
272
+ else:
273
+ await asyncio.sleep(0)
201
274
  self._wake_event.clear()
202
- if not self._event_queue.empty():
203
- return
204
-
205
- if timeout is None:
206
- await self._wake_event.wait()
207
- elif timeout > 0:
208
- with suppress(asyncio.TimeoutError):
209
- await asyncio.wait_for(self._wake_event.wait(), timeout)
210
- else:
211
- await asyncio.sleep(0)
212
275
 
213
276
  async def step(self, timeout: float | None = None) -> None:
214
277
  await self.wait(timeout)
pyglet/app/base.py CHANGED
@@ -5,18 +5,20 @@ import sys
5
5
  import threading
6
6
  from abc import ABC, abstractmethod
7
7
  from queue import Queue
8
- from typing import TYPE_CHECKING, Any, Callable
8
+ from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar
9
9
 
10
- from pyglet import app, clock, event
10
+ from pyglet import app, clock, event, IS_DOC_BUILD
11
+ from pyglet.customtypes import WindowT
11
12
 
12
13
  if TYPE_CHECKING:
14
+ from types import TracebackType
15
+
13
16
  from pyglet.event import EventDispatcher
14
17
  from pyglet.window import BaseWindow
15
18
 
16
- _is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run
17
19
 
18
20
 
19
- class WindowDrawSource(ABC):
21
+ class WindowDrawSource(ABC, Generic[WindowT]):
20
22
  """Control the source of automatic window draw opportunities.
21
23
 
22
24
  Application-driven backends (Win32 and X11) use
@@ -41,17 +43,20 @@ class WindowDrawSource(ABC):
41
43
  def stop(self) -> None:
42
44
  """Stop automatic drawing and release native resources."""
43
45
 
44
- def add_window(self, window: BaseWindow) -> bool:
46
+ def add_window(self, window: WindowT) -> bool:
45
47
  """Add a window created while this source is running."""
46
48
  return True
47
49
 
48
- def remove_window(self, window: BaseWindow) -> None:
50
+ def remove_window(self, window: WindowT) -> None:
49
51
  """Remove a window before its platform resources are destroyed."""
50
52
 
51
53
 
52
- class ClockWindowDrawSource(WindowDrawSource):
54
+ class ClockWindowDrawSource(WindowDrawSource['BaseWindow']):
53
55
  """Application-driven drawing scheduled by the pyglet Clock."""
54
56
 
57
+ _clock: clock.Clock
58
+ _draw_windows: Callable[[float], None]
59
+
55
60
  def __init__(self, app_clock: clock.Clock, draw_windows: Callable[[float], None]) -> None:
56
61
  self._clock = app_clock
57
62
  self._draw_windows = draw_windows
@@ -67,12 +72,19 @@ class ClockWindowDrawSource(WindowDrawSource):
67
72
  self._clock.unschedule(self._draw_windows)
68
73
 
69
74
 
70
- class PlatformEventLoop:
75
+ _EventLoopT = TypeVar('_EventLoopT', bound=event.EventDispatcher)
76
+
77
+
78
+ class PlatformEventLoop(Generic[_EventLoopT]):
71
79
  """Abstract class, implementation depends on platform.
72
80
 
81
+ The type parameter identifies the application loop accepted by the
82
+ platform's window draw source.
83
+
73
84
  .. versionadded:: 1.2
74
85
  """
75
- _event_queue: Queue[tuple[EventDispatcher, str, Any]]
86
+ _event_queue: Queue[tuple[EventDispatcher, str, tuple[Any, ...]]]
87
+ _is_running: threading.Event
76
88
 
77
89
  def __init__(self) -> None: # noqa: D107
78
90
  self._event_queue = queue.Queue()
@@ -127,13 +139,13 @@ class PlatformEventLoop:
127
139
  def start(self) -> None:
128
140
  pass
129
141
 
130
- def step(self, timeout: None | float = None) -> None:
142
+ def step(self, timeout: float | None = None) -> bool | None:
131
143
  raise NotImplementedError('abstract')
132
144
 
133
- def set_timer(self, func: Callable, interval: float) -> None:
145
+ def set_timer(self, func: Callable[[], None] | None, interval: float | None) -> None:
134
146
  pass
135
147
 
136
- def create_window_draw_source(self, event_loop: EventLoop) -> WindowDrawSource | None:
148
+ def create_window_draw_source(self, event_loop: _EventLoopT) -> WindowDrawSource[BaseWindow] | None:
137
149
  """Create a display-driven draw source, or return ``None``.
138
150
 
139
151
  Returning ``None`` selects application-driven Clock drawing.
@@ -159,7 +171,11 @@ class EventLoop(event.EventDispatcher):
159
171
  """
160
172
  _interval: float | None
161
173
  _has_exit_condition: threading.Condition
162
- _has_exit = False
174
+ _has_exit: bool = False
175
+ _window_draw_source: WindowDrawSource[BaseWindow] | None
176
+ _window_draw_exception: tuple[type[BaseException] | None, BaseException | None, TracebackType | None] | None
177
+ clock: clock.Clock
178
+ is_running: bool
163
179
 
164
180
  def __init__(self) -> None: # noqa: D107
165
181
  self._has_exit_condition = threading.Condition()
@@ -319,7 +335,7 @@ class EventLoop(event.EventDispatcher):
319
335
  timeout = self.clock.get_sleep_time(True)
320
336
  app.platform_event_loop.set_timer(self._blocking_timer, timeout)
321
337
 
322
- def idle(self) -> None | float:
338
+ def idle(self) -> float | None:
323
339
  """Called during each iteration of the event loop.
324
340
 
325
341
  The method is called immediately after any window events (i.e., after
@@ -406,7 +422,7 @@ class EventLoop(event.EventDispatcher):
406
422
  if len(app.windows) == 0:
407
423
  self.exit()
408
424
 
409
- if _is_pyglet_doc_run:
425
+ if IS_DOC_BUILD:
410
426
  # Events
411
427
 
412
428
  def on_window_close(self, window: BaseWindow) -> None:
pyglet/app/cocoa.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import signal
4
4
  import time
5
- from typing import TYPE_CHECKING
5
+ from typing import TYPE_CHECKING, Any, cast
6
6
 
7
7
  import pyglet
8
8
  from pyglet import app
@@ -60,7 +60,7 @@ def create_menu():
60
60
  appMenuItem.release()
61
61
 
62
62
 
63
- class _AppDelegate(NSObject):
63
+ class _AppDelegate(NSObject): # type: ignore[valid-type, misc]
64
64
 
65
65
  @objc_method(b'@' + PyObjectEncoding)
66
66
  def init(self, pyglet_loop):
@@ -163,16 +163,20 @@ class CocoaAlternateEventLoop(EventLoop):
163
163
  self.platform_event_loop.nsapp_stop()
164
164
 
165
165
 
166
- class CocoaWindowDrawSource(WindowDrawSource):
166
+ class CocoaWindowDrawSource(WindowDrawSource['CocoaWindow']):
167
167
  """Display driven drawing backed by one CADisplayLink."""
168
168
 
169
+ _active: bool
170
+ _interval: float | None
171
+
169
172
  def __init__(self) -> None:
170
173
  self._active = False
171
174
  self._interval = None
172
175
 
173
176
  def start(self, interval: float) -> bool:
174
- started_windows = []
175
- for window in app.windows:
177
+ started_windows: list[CocoaWindow] = []
178
+ for candidate in app.windows:
179
+ window = cast('CocoaWindow', candidate)
176
180
  if not window._start_display_link(interval): # noqa: SLF001
177
181
  for started_window in started_windows:
178
182
  started_window._stop_display_link() # noqa: SLF001
@@ -190,10 +194,10 @@ class CocoaWindowDrawSource(WindowDrawSource):
190
194
  self._active = False
191
195
  self._interval = None
192
196
  for window in app.windows:
193
- window._stop_display_link() # noqa: SLF001
197
+ cast('CocoaWindow', window)._stop_display_link() # noqa: SLF001
194
198
 
195
199
  def add_window(self, window: CocoaWindow) -> bool:
196
- return not self._active or window._start_display_link(self._interval) # noqa: SLF001
200
+ return not self._active or (self._interval is not None and window._start_display_link(self._interval)) # noqa: SLF001
197
201
 
198
202
  def remove_window(self, window: CocoaWindow) -> None:
199
203
  window._stop_display_link() # noqa: SLF001
@@ -251,7 +255,7 @@ class CocoaPlatformEventLoop(PlatformEventLoop):
251
255
  self.NSApp.activateIgnoringOtherApps_(True)
252
256
  self._finished_launching = True
253
257
 
254
- def create_window_draw_source(self, _event_loop: EventLoop) -> WindowDrawSource | None:
258
+ def create_window_draw_source(self, event_loop: EventLoop) -> WindowDrawSource | None: # noqa: ARG002
255
259
  if pyglet.options.osx_displaylink and self._display_link_supported:
256
260
  return CocoaWindowDrawSource()
257
261
  return None
pyglet/app/ios.py CHANGED
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import os
5
+ from typing import Any
5
6
 
6
7
  from pyglet import app
7
8
  from pyglet.app.base import EventLoop, PlatformEventLoop, WindowDrawSource
@@ -31,10 +32,10 @@ NSRunLoop = ObjCClass('NSRunLoop')
31
32
  CADisplayLink = ObjCClass('CADisplayLink')
32
33
 
33
34
 
34
- class _PygletIOSDisplayLinkTarget(NSObject):
35
+ class _PygletIOSDisplayLinkTarget(NSObject): # type: ignore[valid-type, misc]
35
36
 
36
37
  @objc_method(b'@' + PyObjectEncoding)
37
- def initWithDrawSource_(self, draw_source: IOSDisplayLinkDrawSource) -> ObjCInstance | None:
38
+ def initWithDrawSource_(self: Any, draw_source: IOSDisplayLinkDrawSource) -> ObjCInstance | None:
38
39
  self = ObjCInstance(send_super(self, 'init'))
39
40
  if not self:
40
41
  return None
@@ -49,6 +50,10 @@ class _PygletIOSDisplayLinkTarget(NSObject):
49
50
  class IOSDisplayLinkDrawSource(WindowDrawSource):
50
51
  """One UIKit display link that paces all pyglet window drawing."""
51
52
 
53
+ _target: ObjCInstance | None
54
+ _display_link: ObjCInstance | None
55
+ _event_loop: IOSEventLoop
56
+
52
57
  def __init__(self, event_loop: IOSEventLoop) -> None:
53
58
  self._event_loop = event_loop
54
59
  self._display_link = None
@@ -98,7 +103,7 @@ class IOSDisplayLinkDrawSource(WindowDrawSource):
98
103
  self._last_timestamp, dt = get_display_link_dt(display_link, self._last_timestamp)
99
104
  self._event_loop._tick_app(dt) # noqa: SLF001
100
105
 
101
- class IOSPlatformEventLoop(PlatformEventLoop):
106
+ class IOSPlatformEventLoop(PlatformEventLoop['IOSEventLoop']):
102
107
  """Schedules pyglet work from UIKit's already-running main run loop."""
103
108
 
104
109
  def __init__(self) -> None:
@@ -116,7 +121,7 @@ class IOSPlatformEventLoop(PlatformEventLoop):
116
121
  def stop(self) -> None:
117
122
  self._is_running.clear()
118
123
 
119
- def create_window_draw_source(self, event_loop: EventLoop) -> WindowDrawSource:
124
+ def create_window_draw_source(self, event_loop: IOSEventLoop) -> IOSDisplayLinkDrawSource:
120
125
  return IOSDisplayLinkDrawSource(event_loop)
121
126
 
122
127
 
pyglet/clock.py CHANGED
@@ -1125,7 +1125,7 @@ def chain(
1125
1125
  raise TypeError(
1126
1126
  msg,
1127
1127
  )
1128
- return Chain(generator, clock=clock)
1128
+ return Chain(generator, clock=clock) # type: ignore[arg-type]
1129
1129
 
1130
1130
  return wrapper
1131
1131
 
pyglet/config/__init__.py CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, NamedTuple
5
+ from typing import TYPE_CHECKING, NamedTuple, cast
6
+
7
+ import pyglet
6
8
 
7
9
  from pyglet.enums import GraphicsAPI
8
10
 
@@ -10,7 +12,8 @@ from .base import SurfaceConfig, UserConfig # noqa: TC001
10
12
  from .gl import OpenGLUserConfig, WebGLUserConfig
11
13
 
12
14
  if TYPE_CHECKING:
13
- from pyglet.window import Window
15
+ from pyglet.window import BaseWindow
16
+ from pyglet.window.emscripten import EmscriptenWindow
14
17
 
15
18
 
16
19
  class Config:
@@ -23,19 +26,33 @@ class Config:
23
26
 
24
27
  __slots__ = 'gl2', 'gles2', 'gles3', 'opengl', 'vulkan', 'webgl'
25
28
 
26
- def __init__(self):
27
- self.opengl: OpenGLUserConfig = OpenGLUserConfig(major_version=3, minor_version=3, api=GraphicsAPI.OPENGL)
28
- self.gl2: OpenGLUserConfig = OpenGLUserConfig(major_version=2, minor_version=0, api=GraphicsAPI.OPENGL_2)
29
- self.gles2: OpenGLUserConfig = OpenGLUserConfig(major_version=2, minor_version=0, api=GraphicsAPI.OPENGL_ES_2)
30
- self.gles3: OpenGLUserConfig = OpenGLUserConfig(major_version=3, minor_version=2, api=GraphicsAPI.OPENGL_ES_3)
29
+ def __init__(self) -> None: # noqa: D107
30
+ self.opengl: OpenGLUserConfig = OpenGLUserConfig(
31
+ double_buffer=True, depth_size=24, major_version=3, minor_version=3, api=GraphicsAPI.OPENGL,
32
+ )
33
+ self.gl2: OpenGLUserConfig = OpenGLUserConfig(
34
+ double_buffer=True, depth_size=24, major_version=2, minor_version=0, api=GraphicsAPI.OPENGL_2,
35
+ )
36
+ self.gles2: OpenGLUserConfig = OpenGLUserConfig(
37
+ double_buffer=True, depth_size=24, major_version=2, minor_version=0, api=GraphicsAPI.OPENGL_ES_2,
38
+ )
39
+ self.gles3: OpenGLUserConfig = OpenGLUserConfig(
40
+ double_buffer=True, depth_size=24, major_version=3, minor_version=2, api=GraphicsAPI.OPENGL_ES_3,
41
+ )
31
42
  self.webgl: WebGLUserConfig = WebGLUserConfig()
32
43
  # self.vulkan: TBD
33
44
 
34
45
 
35
- def match_surface_config(config: UserConfig, surface: Window) -> SurfaceConfig | None:
36
- if isinstance(config, (OpenGLUserConfig, WebGLUserConfig)):
46
+ def match_surface_config(config: UserConfig, surface: BaseWindow) -> SurfaceConfig | None:
47
+ if isinstance(config, OpenGLUserConfig):
37
48
  from pyglet.config.gl import get_surface_config # noqa: PLC0415
38
49
  return get_surface_config(config, surface)
39
50
 
51
+ if isinstance(config, WebGLUserConfig):
52
+ if pyglet.compat_platform == 'emscripten':
53
+ from pyglet.config.gl.webgl import match # noqa: PLC0415
54
+ return match(config, cast('EmscriptenWindow', surface))
55
+ return None
56
+
40
57
  msg = f"Matching for '{config.__class__}' is not yet implemented."
41
58
  raise NotImplementedError(msg)
pyglet/config/base.py CHANGED
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, TYPE_CHECKING
3
+ from typing import Any, Generic, TYPE_CHECKING
4
+
5
+ from pyglet.customtypes import WindowT
4
6
 
5
7
  if TYPE_CHECKING:
6
- from pyglet.window import Window
7
8
  from pyglet.enums import GraphicsAPI
8
9
 
9
10
 
@@ -16,12 +17,16 @@ class UserConfig:
16
17
  return False
17
18
 
18
19
 
19
- class SurfaceConfig:
20
+ class SurfaceConfig(Generic[WindowT]):
20
21
  """Surface configuration information returned by a platform.
21
22
 
22
23
  This describes the information a desired surface or window can support.
23
24
  """
24
- def __init__(self, window: Window, config: UserConfig, handle: Any) -> None:
25
+ _window: WindowT
26
+ config: UserConfig
27
+ handle: Any
28
+
29
+ def __init__(self, window: WindowT, config: UserConfig, handle: Any) -> None:
25
30
  """A config representing the capabilities of a specific surface.
26
31
 
27
32
  This is returned by the platform on a specific window and is not meant to be created by users.
@@ -1,16 +1,35 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
+ from typing import Any, TYPE_CHECKING, cast
4
5
 
5
6
  import pyglet
6
7
 
7
8
  from pyglet.enums import GraphicsAPI
8
9
  from pyglet.config.base import UserConfig, SurfaceConfig
10
+ from pyglet.customtypes import WindowT
9
11
 
12
+ if TYPE_CHECKING:
13
+ from pyglet.graphics.api.gl.context import OpenGLSurfaceContext
14
+ from pyglet.graphics.api.gl.global_opengl import OpenGLBackend
15
+ from pyglet.window import BaseWindow
16
+ from pyglet.window.apple.cocoa import CocoaWindow
17
+ from pyglet.window.apple.ios import IOSWindow
18
+ from pyglet.window.headless import HeadlessWindow
19
+ from pyglet.window.wayland import WaylandWindow
20
+ from pyglet.window.win32 import Win32Window
21
+ from pyglet.window.xlib import XlibWindow
10
22
 
11
- class GLSurfaceConfig(SurfaceConfig):
23
+
24
+ class GLSurfaceConfig(SurfaceConfig[WindowT]):
12
25
  config: OpenGLUserConfig
13
26
 
27
+ def __init__(self, window: WindowT, config: OpenGLUserConfig, handle: Any) -> None:
28
+ super().__init__(window, config, handle)
29
+
30
+ def create_context(self, opengl_backend: OpenGLBackend, share: Any) -> OpenGLSurfaceContext:
31
+ raise NotImplementedError
32
+
14
33
 
15
34
  # Platform Specific Configurations
16
35
 
@@ -98,36 +117,31 @@ class WebGLUserConfig(UserConfig):
98
117
  return False
99
118
 
100
119
 
101
- def get_surface_config(user_config: UserConfig, surface: pyglet.window.Window) -> SurfaceConfig | None:
120
+ def get_surface_config(user_config: OpenGLUserConfig, surface: BaseWindow) -> SurfaceConfig | None:
102
121
 
103
122
  if pyglet.options.headless or pyglet.options.wayland:
104
- from pyglet.config.gl.egl import match
123
+ from pyglet.config.gl.egl import match as match_egl # noqa: PLC0415
105
124
 
106
- return match(user_config, surface)
125
+ return match_egl(user_config, cast("HeadlessWindow | WaylandWindow", surface))
107
126
 
108
127
  if pyglet.compat_platform == "win32":
109
- from pyglet.config.gl.windows import match # noqa: PLC0415
128
+ from pyglet.config.gl.windows import match as match_windows # noqa: PLC0415
110
129
 
111
- return match(user_config, surface)
130
+ return match_windows(user_config, cast("Win32Window", surface))
112
131
 
113
132
  if pyglet.compat_platform.startswith("linux"):
114
- from pyglet.config.gl.x11 import match # noqa: PLC0415
133
+ from pyglet.config.gl.x11 import match as match_x11 # noqa: PLC0415
115
134
 
116
- return match(user_config, surface)
135
+ return match_x11(user_config, cast("XlibWindow", surface))
117
136
 
118
137
  if pyglet.compat_platform == "darwin":
119
- from pyglet.config.gl.macos import match # noqa: PLC0415
138
+ from pyglet.config.gl.macos import match as match_macos # noqa: PLC0415
120
139
 
121
- return match(user_config, surface)
140
+ return match_macos(user_config, cast("CocoaWindow", surface))
122
141
 
123
142
  if pyglet.compat_platform == "ios":
124
- from pyglet.config.gl.ios import match # noqa: PLC0415
125
-
126
- return match(user_config, surface)
127
-
128
- if pyglet.compat_platform == "emscripten":
129
- from pyglet.config.gl.webgl import match # noqa: PLC0415
143
+ from pyglet.config.gl.ios import match as match_ios # noqa: PLC0415
130
144
 
131
- return match(user_config, surface)
145
+ return match_ios(user_config, cast("IOSWindow", surface))
132
146
 
133
147
  return None