pyglet 3.0.dev7__py3-none-any.whl → 3.0.dev9__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 +22 -13
- pyglet/__init__.pyi +1 -1
- pyglet/app/__init__.py +2 -11
- pyglet/app/async_app.py +196 -121
- pyglet/app/base.py +128 -15
- pyglet/clock.py +155 -55
- pyglet/config/gl/egl.py +1 -1
- pyglet/display/__init__.py +1 -1
- pyglet/display/base.py +1 -1
- pyglet/display/headless.py +29 -6
- pyglet/display/wayland.py +1 -1
- pyglet/enums.py +14 -0
- pyglet/experimental/flaccodec.py +2 -2
- pyglet/experimental/geoshader_sprite.py +18 -16
- pyglet/experimental/particles.py +12 -11
- pyglet/font/__init__.py +30 -8
- pyglet/font/base.py +40 -0
- pyglet/font/dwrite/__init__.py +251 -78
- pyglet/font/dwrite/d2d1_lib.py +145 -9
- pyglet/font/dwrite/d2d1_types_lib.py +43 -0
- pyglet/font/dwrite/dwrite_lib.py +1 -1
- pyglet/font/fontconfig.py +37 -1
- pyglet/font/freetype.py +160 -9
- pyglet/font/freetype_lib.py +47 -1
- pyglet/font/group.py +120 -83
- pyglet/font/pyodide_js.py +112 -37
- pyglet/font/quartz.py +141 -20
- pyglet/font/ttf.py +17 -1
- pyglet/font/user.py +4 -0
- pyglet/font/win32.py +18 -0
- pyglet/graphics/__init__.py +14 -3
- pyglet/graphics/api/__init__.py +1 -3
- pyglet/graphics/api/base.py +75 -70
- pyglet/graphics/api/gl/__init__.py +1 -1
- pyglet/graphics/api/gl/base.py +42 -11
- pyglet/graphics/api/gl/buffer.py +25 -25
- pyglet/graphics/api/gl/context.py +30 -35
- pyglet/graphics/api/gl/egl/context.py +1 -1
- pyglet/graphics/api/gl/enums.py +2 -1
- pyglet/graphics/api/gl/framebuffer.py +15 -20
- pyglet/graphics/api/gl/gl_info.py +109 -1
- pyglet/graphics/api/gl/global_opengl.py +1 -6
- pyglet/graphics/api/gl/shader.py +47 -120
- pyglet/graphics/api/gl/shapes.py +1 -1
- pyglet/graphics/api/gl/sprite.py +3 -3
- pyglet/graphics/api/gl/state.py +13 -9
- pyglet/graphics/api/gl/text.py +22 -5
- pyglet/graphics/api/gl/texture.py +378 -144
- pyglet/graphics/api/gl/vertexarray.py +8 -7
- pyglet/graphics/api/gl/vertexdomain.py +2 -3
- pyglet/graphics/api/gl2/buffer.py +2 -2
- pyglet/graphics/api/gl2/framebuffer.py +1 -3
- pyglet/graphics/api/gl2/shader.py +2 -1
- pyglet/graphics/api/gl2/shapes.py +2 -2
- pyglet/graphics/api/gl2/sprite.py +2 -2
- pyglet/graphics/api/gl2/text.py +22 -5
- pyglet/graphics/api/gl2/vertexdomain.py +0 -2
- pyglet/graphics/api/webgl/__init__.py +0 -18
- pyglet/graphics/api/webgl/buffer.py +67 -28
- pyglet/graphics/api/webgl/context.py +9 -54
- pyglet/graphics/api/webgl/enums.py +2 -1
- pyglet/graphics/api/webgl/framebuffer.py +13 -20
- pyglet/graphics/api/webgl/gl_info.py +48 -2
- pyglet/graphics/api/webgl/shader.py +20 -102
- pyglet/graphics/api/webgl/shapes.py +1 -1
- pyglet/graphics/api/webgl/sprite.py +3 -3
- pyglet/graphics/api/webgl/state.py +13 -15
- pyglet/graphics/api/webgl/text.py +22 -5
- pyglet/graphics/api/webgl/texture.py +217 -235
- pyglet/graphics/api/webgl/vertexarray.py +8 -7
- pyglet/graphics/atlas.py +4 -4
- pyglet/graphics/buffer.py +27 -4
- pyglet/graphics/draw.py +31 -10
- pyglet/graphics/framebuffer.py +2 -2
- pyglet/graphics/resource.py +124 -0
- pyglet/graphics/shader.py +306 -42
- pyglet/graphics/texture.py +142 -27
- pyglet/graphics/vertexdomain.py +18 -1
- pyglet/gui/ninepatch.py +6 -6
- pyglet/gui/widgets.py +9 -8
- pyglet/image/__init__.py +28 -2
- pyglet/image/codecs/__init__.py +8 -0
- pyglet/image/codecs/js_image.py +61 -76
- pyglet/image/codecs/pil.py +2 -2
- pyglet/image/codecs/wic.py +106 -84
- pyglet/info.py +402 -47
- pyglet/libs/darwin/cocoapy/cocoalibs.py +24 -0
- pyglet/libs/egl/__init__.py +2 -0
- pyglet/libs/{linux/egl → egl}/eglext.py +2 -2
- pyglet/libs/emscripten/__init__.py +72 -0
- pyglet/libs/emscripten/files.py +45 -0
- pyglet/libs/emscripten/filesystem.py +63 -0
- pyglet/libs/emscripten/proxies.py +55 -0
- pyglet/libs/emscripten/pyglet_emscripten.js +79 -0
- pyglet/libs/win32/__init__.py +2 -0
- pyglet/libs/win32/com.py +16 -4
- pyglet/media/codecs/base.py +88 -110
- pyglet/media/codecs/coreaudio.py +2 -2
- pyglet/media/codecs/ffmpeg.py +17 -59
- pyglet/media/codecs/ffmpeg_lib/libswresample.py +0 -3
- pyglet/media/codecs/gstreamer.py +2 -5
- pyglet/media/codecs/pyogg.py +4 -4
- pyglet/media/codecs/wave.py +2 -4
- pyglet/media/codecs/webaudio_pyodide.py +1 -1
- pyglet/media/codecs/wmf.py +5 -10
- pyglet/media/devices/base.py +37 -29
- pyglet/media/drivers/base.py +98 -142
- pyglet/media/drivers/directsound/adaptation.py +2 -5
- pyglet/media/drivers/openal/adaptation.py +2 -7
- pyglet/media/drivers/pulse/adaptation.py +3 -5
- pyglet/media/drivers/pyodide_js/adaptation.py +1 -34
- pyglet/media/drivers/silent/adaptation.py +2 -6
- pyglet/media/drivers/xaudio2/adaptation.py +222 -89
- pyglet/media/drivers/xaudio2/interface.py +469 -293
- pyglet/media/player.py +28 -2
- pyglet/media/player_worker_thread.py +4 -3
- pyglet/media/synthesis.py +2 -4
- pyglet/model/__init__.py +24 -23
- pyglet/model/codecs/obj.py +15 -9
- pyglet/resource.py +2 -2
- pyglet/shapes.py +56 -56
- pyglet/sprite.py +15 -15
- pyglet/storage.py +398 -0
- pyglet/text/__init__.py +105 -25
- pyglet/text/caret.py +20 -20
- pyglet/text/document.py +23 -1
- pyglet/text/effects.py +62 -0
- pyglet/text/formats/html.py +26 -3
- pyglet/text/formats/structured.py +67 -7
- pyglet/text/layout/__init__.py +9 -1
- pyglet/text/layout/base.py +191 -986
- pyglet/text/layout/boxes.py +1038 -0
- pyglet/text/layout/flow.py +510 -0
- pyglet/text/layout/incremental.py +32 -4
- pyglet/text/layout/scrolling.py +13 -3
- pyglet/window/__init__.py +24 -10
- pyglet/window/camera/base.py +7 -3
- pyglet/window/camera/camera2d.py +3 -2
- pyglet/window/camera/camera3d.py +3 -2
- pyglet/window/dialog/__init__.py +14 -4
- pyglet/window/dialog/base.py +34 -3
- pyglet/window/dialog/darwin.py +4 -4
- pyglet/window/dialog/emscripten.py +74 -0
- pyglet/window/dialog/linux.py +6 -4
- pyglet/window/dialog/windows.py +2 -2
- pyglet/window/emscripten/__init__.py +114 -21
- pyglet/window/headless/__init__.py +42 -18
- pyglet/window/wayland/__init__.py +1 -1
- {pyglet-3.0.dev7.dist-info → pyglet-3.0.dev9.dist-info}/METADATA +23 -3
- {pyglet-3.0.dev7.dist-info → pyglet-3.0.dev9.dist-info}/RECORD +154 -143
- pyglet/libs/linux/egl/__init__.py +0 -2
- /pyglet/libs/{linux/egl → egl}/egl.py +0 -0
- /pyglet/libs/{linux/egl → egl}/lib.py +0 -0
- {pyglet-3.0.dev7.dist-info → pyglet-3.0.dev9.dist-info}/WHEEL +0 -0
- {pyglet-3.0.dev7.dist-info → pyglet-3.0.dev9.dist-info}/licenses/LICENSE +0 -0
pyglet/__init__.py
CHANGED
|
@@ -18,12 +18,15 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from typing import Any, Callable, ItemsView, Sized
|
|
19
19
|
|
|
20
20
|
#: The release version
|
|
21
|
-
version = '3.0.
|
|
21
|
+
version = '3.0.dev9'
|
|
22
22
|
__version__ = version
|
|
23
23
|
|
|
24
24
|
MIN_PYTHON_VERSION = 3, 10
|
|
25
25
|
MIN_PYTHON_VERSION_STR = ".".join([str(v) for v in MIN_PYTHON_VERSION])
|
|
26
26
|
|
|
27
|
+
#: The Pyodide release used to develop and test pyglet's browser support.
|
|
28
|
+
PYODIDE_VERSION = "0.29.4"
|
|
29
|
+
|
|
27
30
|
if sys.version_info < MIN_PYTHON_VERSION:
|
|
28
31
|
msg = f"pyglet {version} requires Python {MIN_PYTHON_VERSION_STR} or newer."
|
|
29
32
|
raise Exception(msg)
|
|
@@ -56,7 +59,6 @@ class PyodideOptions:
|
|
|
56
59
|
page, and do not want to alter your code, then modify this option.
|
|
57
60
|
"""
|
|
58
61
|
|
|
59
|
-
|
|
60
62
|
@dataclass
|
|
61
63
|
class Options:
|
|
62
64
|
"""Dataclass for global pyglet options."""
|
|
@@ -208,25 +210,30 @@ class Options:
|
|
|
208
210
|
.. versionadded:: 2.0
|
|
209
211
|
"""
|
|
210
212
|
|
|
211
|
-
|
|
212
|
-
"""If ``True``,
|
|
213
|
-
Attempt to parse fonts by the passed name, to best match legacy RBIZ naming.
|
|
213
|
+
font_name_compatibility: bool = False
|
|
214
|
+
"""If ``True``, pyglet performs additional compatibility lookup for font names.
|
|
214
215
|
|
|
215
|
-
|
|
216
|
+
The portable spelling of a font is its family name with explicit ``weight``, ``style``, and ``stretch``. This
|
|
217
|
+
option additionally accepts OpenType full names and platform compatibility aliases where the backend supports
|
|
218
|
+
them. For example, it allows ``"Arial Narrow"`` rather than ``"Arial"`` with a ``"condensed"`` stretch, or
|
|
219
|
+
``"Arial Black"`` instead of ``"Arial"`` with a weight of ``"black"``.
|
|
216
220
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
This option does not reject full names when disabled: a platform font API may still accept them natively. It only
|
|
222
|
+
enables pyglet's extra compatibility lookup and mapping, which can improve cross-platform behavior at the cost of
|
|
223
|
+
slower font resolution.
|
|
220
224
|
|
|
221
|
-
|
|
222
|
-
|
|
225
|
+
On Windows with DirectWrite, GDI/RBIZ aliases are not indexed as family names. Resolving one requires enumerating
|
|
226
|
+
installed font families and faces, and can be noticeably slow on systems with many installed fonts. Results are
|
|
227
|
+
cached until a custom font is added.
|
|
228
|
+
|
|
229
|
+
:see: https://learn.microsoft.com/en-us/windows/win32/directwrite/font-selection#rbiz-font-family-model
|
|
223
230
|
|
|
224
231
|
.. note:: Due to the high variation of styles and limited capability of some fonts, there is no guarantee the
|
|
225
|
-
|
|
232
|
+
selected face will be exactly what the user wants.
|
|
226
233
|
|
|
227
234
|
.. note:: The ``debug_font`` option can provide information on what settings are being selected.
|
|
228
235
|
|
|
229
|
-
.. versionadded::
|
|
236
|
+
.. versionadded:: 3.0
|
|
230
237
|
"""
|
|
231
238
|
|
|
232
239
|
win32_disable_xinput: bool = False
|
|
@@ -502,6 +509,7 @@ if TYPE_CHECKING:
|
|
|
502
509
|
resource,
|
|
503
510
|
shapes,
|
|
504
511
|
sprite,
|
|
512
|
+
storage,
|
|
505
513
|
text,
|
|
506
514
|
window,
|
|
507
515
|
)
|
|
@@ -525,6 +533,7 @@ else:
|
|
|
525
533
|
resource = _ModuleProxy("resource") # type: ignore
|
|
526
534
|
sprite = _ModuleProxy("sprite") # type: ignore
|
|
527
535
|
shapes = _ModuleProxy("shapes") # type: ignore
|
|
536
|
+
storage = _ModuleProxy("storage") # type: ignore
|
|
528
537
|
text = _ModuleProxy("text") # type: ignore
|
|
529
538
|
window = _ModuleProxy("window") # type: ignore
|
|
530
539
|
|
pyglet/__init__.pyi
CHANGED
pyglet/app/__init__.py
CHANGED
|
@@ -80,11 +80,7 @@ def run(interval: float | None = 1 / 60) -> None:
|
|
|
80
80
|
pyglet.app.event_loop.run(interval)
|
|
81
81
|
|
|
82
82
|
"""
|
|
83
|
-
|
|
84
|
-
import asyncio
|
|
85
|
-
asyncio.create_task(event_loop.run(interval)) # noqa: RUF006
|
|
86
|
-
else:
|
|
87
|
-
event_loop.run(interval)
|
|
83
|
+
event_loop.run(interval)
|
|
88
84
|
|
|
89
85
|
|
|
90
86
|
def exit() -> None:
|
|
@@ -99,12 +95,7 @@ def exit() -> None:
|
|
|
99
95
|
pyglet.app.event_loop.exit()
|
|
100
96
|
|
|
101
97
|
"""
|
|
102
|
-
|
|
103
|
-
import asyncio
|
|
104
|
-
|
|
105
|
-
asyncio.create_task(event_loop.exit())
|
|
106
|
-
else:
|
|
107
|
-
event_loop.exit()
|
|
98
|
+
event_loop.exit()
|
|
108
99
|
|
|
109
100
|
|
|
110
101
|
#: The global event loop. Applications can replace this
|
pyglet/app/async_app.py
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
1
|
+
"""Asyncio event-loop support for the Emscripten platform."""
|
|
1
2
|
from __future__ import annotations
|
|
2
3
|
|
|
3
4
|
import asyncio
|
|
5
|
+
from contextlib import suppress
|
|
4
6
|
import sys
|
|
5
7
|
from typing import Any, Callable, TYPE_CHECKING
|
|
6
8
|
|
|
7
9
|
import pyglet
|
|
8
10
|
from pyglet import app, clock, event
|
|
11
|
+
from pyglet.app.base import ClockWindowDrawSource, WindowDrawSource
|
|
9
12
|
|
|
10
13
|
if TYPE_CHECKING:
|
|
11
14
|
from pyglet.event import EventDispatcher
|
|
12
15
|
from pyglet.window import BaseWindow
|
|
13
16
|
|
|
14
|
-
_is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run
|
|
15
17
|
try:
|
|
16
|
-
from js import requestAnimationFrame, performance
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
from js import cancelAnimationFrame, requestAnimationFrame, performance
|
|
19
|
+
except ImportError as exc:
|
|
20
|
+
raise ImportError('Pyodide not available.') from exc
|
|
21
|
+
|
|
22
|
+
from pyglet.libs.emscripten.proxies import ProxyRegistry
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class AsyncEventLoop(event.EventDispatcher):
|
|
23
|
-
"""
|
|
26
|
+
"""Browser event loop which preserves pyglet's normal clock semantics."""
|
|
24
27
|
|
|
25
28
|
def __init__(self) -> None:
|
|
29
|
+
"""Create the browser clock and callback state."""
|
|
26
30
|
self._has_exit_event = asyncio.Event()
|
|
27
|
-
# Change the default clock to use the javascript performance timer.
|
|
28
31
|
self.clock = clock.Clock(self._get_js_time)
|
|
29
32
|
pyglet.clock.set_default(self.clock)
|
|
30
33
|
self.is_running = False
|
|
31
|
-
self._interval = None
|
|
32
|
-
self.
|
|
33
|
-
self.
|
|
34
|
-
|
|
35
|
-
self._frame_future = None
|
|
36
|
-
self._frame_proxy = create_proxy(self._frame_cb)
|
|
34
|
+
self._interval: float | None = None
|
|
35
|
+
self._run_task: asyncio.Task[None] | None = None
|
|
36
|
+
self._window_draw_source: WindowDrawSource | None = None
|
|
37
|
+
self._window_draw_exception: tuple[type[BaseException], BaseException, Any] | None = None
|
|
37
38
|
|
|
38
39
|
def _get_js_time(self) -> float:
|
|
39
40
|
"""Use Javascripts performance.now for the clock accuracy.
|
|
@@ -42,171 +43,245 @@ class AsyncEventLoop(event.EventDispatcher):
|
|
|
42
43
|
"""
|
|
43
44
|
return performance.now() / 1000
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
def _redraw_windows(self, dt: float) -> None:
|
|
47
|
+
for window in tuple(app.windows):
|
|
48
|
+
self._dispatch_platform_draw(window, dt)
|
|
49
|
+
|
|
50
|
+
def _dispatch_platform_draw(self, window: BaseWindow, dt: float) -> None:
|
|
51
|
+
try:
|
|
49
52
|
window.draw(dt)
|
|
53
|
+
except BaseException: # noqa: BLE001 - JS callbacks cannot unwind Python exceptions.
|
|
54
|
+
self._window_draw_exception = sys.exc_info()
|
|
55
|
+
self.exit()
|
|
50
56
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
_frame_dt = now - self._last_ts
|
|
57
|
+
def _raise_window_draw_exception(self) -> None:
|
|
58
|
+
if self._window_draw_exception is None:
|
|
59
|
+
return
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
_, exception, traceback = self._window_draw_exception
|
|
62
|
+
self._window_draw_exception = None
|
|
63
|
+
raise exception.with_traceback(traceback)
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
def _schedule_window_draw(self, interval: float | None) -> None:
|
|
66
|
+
self._interval = interval
|
|
67
|
+
if interval is None:
|
|
68
|
+
return
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return await self._frame_future
|
|
70
|
+
draw_source = app.platform_event_loop.create_window_draw_source(self)
|
|
71
|
+
if draw_source is None:
|
|
72
|
+
draw_source = ClockWindowDrawSource(self.clock, self._redraw_windows)
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
self._window_draw_source = draw_source
|
|
75
|
+
if not draw_source.start(interval):
|
|
76
|
+
draw_source.stop()
|
|
77
|
+
self._window_draw_source = ClockWindowDrawSource(self.clock, self._redraw_windows)
|
|
78
|
+
self._window_draw_source.start(interval)
|
|
70
79
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
self.
|
|
80
|
+
def _unschedule_window_draw(self) -> None:
|
|
81
|
+
if self._window_draw_source is not None:
|
|
82
|
+
self._window_draw_source.stop()
|
|
83
|
+
self._window_draw_source = None
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
def run(self, interval: float | None = 1 / 60) -> None:
|
|
86
|
+
"""Start processing browser events, scheduled functions, and window draws."""
|
|
87
|
+
if self._run_task is not None and not self._run_task.done():
|
|
88
|
+
raise RuntimeError("The pyglet event loop is already running.")
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
self.is_running = True
|
|
90
|
+
task = asyncio.create_task(self._run(interval))
|
|
91
|
+
self._run_task = task
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
if self.
|
|
85
|
-
|
|
86
|
-
await self.draw_frame()
|
|
87
|
-
self.idle() # Ticks clock and runs scheduled functions.
|
|
88
|
-
await platform_event_loop.dispatch_posted_events()
|
|
89
|
-
else:
|
|
90
|
-
_schedule_rate = self._interval / 1000.0
|
|
93
|
+
def clear_task(completed_task: asyncio.Task[None]) -> None:
|
|
94
|
+
if self._run_task is completed_task:
|
|
95
|
+
self._run_task = None
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
async def _draw_loop() -> None:
|
|
94
|
-
while not self._has_exit_event.is_set():
|
|
95
|
-
await self.draw_frame()
|
|
97
|
+
task.add_done_callback(clear_task)
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
async def _run(self, interval: float | None = 1 / 60) -> None:
|
|
100
|
+
"""Process pyglet clock callbacks and browser events."""
|
|
101
|
+
if self.is_running:
|
|
102
|
+
raise RuntimeError("The pyglet event loop is already running.")
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await asyncio.sleep(0 if timeout is None else timeout)
|
|
104
|
+
self._interval = interval
|
|
105
|
+
self._has_exit_event.clear()
|
|
106
|
+
platform_event_loop = app.platform_event_loop
|
|
103
107
|
|
|
108
|
+
try:
|
|
109
|
+
platform_event_loop.start()
|
|
110
|
+
self._schedule_window_draw(interval)
|
|
111
|
+
self.dispatch_event('on_enter')
|
|
112
|
+
self.is_running = True
|
|
113
|
+
|
|
114
|
+
while not self._has_exit_event.is_set():
|
|
115
|
+
timeout = self.idle()
|
|
116
|
+
await platform_event_loop.wait(timeout)
|
|
117
|
+
platform_event_loop.dispatch_posted_events()
|
|
118
|
+
finally:
|
|
104
119
|
self.is_running = False
|
|
120
|
+
platform_event_loop.stop()
|
|
121
|
+
self._unschedule_window_draw()
|
|
105
122
|
self.dispatch_event('on_exit')
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
traceback.print_exc()
|
|
111
|
-
|
|
112
|
-
async def enter_blocking(self) -> None:
|
|
113
|
-
"""Ensure `idle()` continues to be called during blocking operations."""
|
|
123
|
+
|
|
124
|
+
self._raise_window_draw_exception()
|
|
125
|
+
|
|
126
|
+
def enter_blocking(self) -> None:
|
|
114
127
|
timeout = self.idle()
|
|
115
|
-
|
|
128
|
+
app.platform_event_loop.set_timer(self._blocking_timer, timeout)
|
|
116
129
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
await app.platform_event_loop.set_timer(None, None)
|
|
130
|
+
def exit_blocking(self) -> None:
|
|
131
|
+
app.platform_event_loop.set_timer(None, None)
|
|
120
132
|
|
|
121
|
-
|
|
122
|
-
"""Called to process scheduled events during blocking operations."""
|
|
133
|
+
def _blocking_timer(self) -> None:
|
|
123
134
|
dt = self.clock.update_time()
|
|
124
135
|
self.clock.call_scheduled_functions(dt)
|
|
125
|
-
if self._interval is None:
|
|
126
|
-
self._redraw_windows(dt)
|
|
127
|
-
|
|
128
136
|
timeout = self.clock.get_sleep_time(True)
|
|
129
|
-
|
|
137
|
+
app.platform_event_loop.set_timer(self._blocking_timer, timeout)
|
|
130
138
|
|
|
131
139
|
def idle(self) -> float | None:
|
|
132
|
-
"""Called each iteration to process events."""
|
|
133
140
|
dt = self.clock.update_time()
|
|
134
141
|
self.clock.call_scheduled_functions(dt)
|
|
135
142
|
return self.clock.get_sleep_time(True)
|
|
136
143
|
|
|
137
|
-
|
|
138
|
-
"""Exit the event loop asynchronously."""
|
|
144
|
+
def exit(self) -> None:
|
|
139
145
|
self._has_exit_event.set()
|
|
140
146
|
app.platform_event_loop.notify()
|
|
141
147
|
|
|
142
148
|
async def sleep(self, timeout: float) -> bool:
|
|
143
|
-
"""Pause execution for a given time unless `exit()` is called."""
|
|
144
149
|
try:
|
|
145
150
|
await asyncio.wait_for(self._has_exit_event.wait(), timeout)
|
|
146
151
|
return True
|
|
147
152
|
except asyncio.TimeoutError:
|
|
148
153
|
return False
|
|
149
154
|
|
|
150
|
-
|
|
151
|
-
"""Exit when the last window is closed."""
|
|
155
|
+
def on_window_close(self, _window: BaseWindow) -> None:
|
|
152
156
|
if len(app.windows) == 0:
|
|
153
|
-
|
|
157
|
+
self.exit()
|
|
158
|
+
|
|
154
159
|
|
|
155
160
|
AsyncEventLoop.register_event_type('on_window_close')
|
|
156
161
|
AsyncEventLoop.register_event_type('on_enter')
|
|
157
162
|
AsyncEventLoop.register_event_type('on_exit')
|
|
158
163
|
|
|
164
|
+
|
|
159
165
|
class AsyncPlatformEventLoop:
|
|
160
|
-
"""An asyncio-based platform event loop."""
|
|
166
|
+
"""An asyncio-based platform event loop, currently for supporting Pyodide."""
|
|
161
167
|
|
|
162
168
|
def __init__(self) -> None:
|
|
163
|
-
"""
|
|
164
|
-
self._event_queue = asyncio.Queue()
|
|
165
|
-
self._is_running =
|
|
169
|
+
"""Create the browser event queue and wakeup state."""
|
|
170
|
+
self._event_queue: asyncio.Queue[tuple[EventDispatcher, str, tuple[Any, ...]]] = asyncio.Queue()
|
|
171
|
+
self._is_running = False
|
|
172
|
+
self._wake_event = asyncio.Event()
|
|
173
|
+
self._timer_task: asyncio.Task[None] | None = None
|
|
166
174
|
|
|
167
175
|
def is_running(self) -> bool:
|
|
168
|
-
|
|
169
|
-
return self._is_running.is_set()
|
|
176
|
+
return self._is_running
|
|
170
177
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
await self._event_queue.put((dispatcher, event, args))
|
|
178
|
+
def post_event(self, dispatcher: EventDispatcher, event: str, *args: Any) -> None:
|
|
179
|
+
self._event_queue.put_nowait((dispatcher, event, args))
|
|
174
180
|
self.notify()
|
|
175
181
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
while not self._event_queue.empty():
|
|
182
|
+
def dispatch_posted_events(self) -> None:
|
|
183
|
+
while True:
|
|
179
184
|
try:
|
|
180
|
-
dispatcher,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
# weakly-referenced object no longer exists
|
|
186
|
-
pass
|
|
185
|
+
dispatcher, event, args = self._event_queue.get_nowait()
|
|
186
|
+
except asyncio.QueueEmpty:
|
|
187
|
+
return
|
|
188
|
+
with suppress(ReferenceError):
|
|
189
|
+
dispatcher.dispatch_event(event, *args)
|
|
187
190
|
|
|
188
191
|
def notify(self) -> None:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
self._wake_event.set()
|
|
193
|
+
|
|
194
|
+
def start(self) -> None:
|
|
195
|
+
self._is_running = True
|
|
196
|
+
|
|
197
|
+
async def wait(self, timeout: float | None) -> None:
|
|
198
|
+
if not self._event_queue.empty():
|
|
199
|
+
return
|
|
192
200
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
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)
|
|
196
212
|
|
|
197
213
|
async def step(self, timeout: float | None = None) -> None:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
214
|
+
await self.wait(timeout)
|
|
215
|
+
self.dispatch_posted_events()
|
|
216
|
+
|
|
217
|
+
def set_timer(self, func: Callable | None, interval: float | None) -> None:
|
|
218
|
+
if self._timer_task is not None:
|
|
219
|
+
self._timer_task.cancel()
|
|
220
|
+
self._timer_task = None
|
|
221
|
+
|
|
222
|
+
if func is None or interval is None or not self.is_running():
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
async def timer() -> None:
|
|
226
|
+
while self.is_running():
|
|
227
|
+
await asyncio.sleep(interval)
|
|
228
|
+
if self.is_running():
|
|
229
|
+
func()
|
|
230
|
+
|
|
231
|
+
self._timer_task = asyncio.create_task(timer())
|
|
232
|
+
|
|
233
|
+
def create_window_draw_source(self, event_loop: AsyncEventLoop) -> WindowDrawSource:
|
|
234
|
+
return RequestAnimationFrameDrawSource(event_loop)
|
|
235
|
+
|
|
236
|
+
def stop(self) -> None:
|
|
237
|
+
self._is_running = False
|
|
238
|
+
self.set_timer(None, None)
|
|
239
|
+
self.notify()
|
|
203
240
|
|
|
204
|
-
async def set_timer(self, func: Callable, interval: float) -> None:
|
|
205
|
-
"""Schedule a timer function asynchronously."""
|
|
206
|
-
while self.is_running():
|
|
207
|
-
await asyncio.sleep(interval)
|
|
208
|
-
func()
|
|
209
241
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
242
|
+
class RequestAnimationFrameDrawSource(WindowDrawSource):
|
|
243
|
+
"""Draw browser windows when the browser requests an animation frame."""
|
|
244
|
+
|
|
245
|
+
def __init__(self, event_loop: AsyncEventLoop) -> None:
|
|
246
|
+
"""Attaches to the browser event loop."""
|
|
247
|
+
self._event_loop = event_loop
|
|
248
|
+
self._last_timestamp: float | None = None
|
|
249
|
+
self._request_id: int | None = None
|
|
250
|
+
self._running = False
|
|
251
|
+
self._callback = None
|
|
252
|
+
self._proxies = ProxyRegistry()
|
|
253
|
+
|
|
254
|
+
def start(self, _interval: float) -> bool:
|
|
255
|
+
self._running = True
|
|
256
|
+
self._last_timestamp = None
|
|
257
|
+
if self._callback is None:
|
|
258
|
+
self._callback = self._proxies.create(self._on_animation_frame)
|
|
259
|
+
self._request_next_frame()
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
def stop(self) -> None:
|
|
263
|
+
if not self._running:
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
self._running = False
|
|
267
|
+
if self._request_id is not None:
|
|
268
|
+
cancelAnimationFrame(self._request_id)
|
|
269
|
+
self._request_id = None
|
|
270
|
+
self._proxies.destroy()
|
|
271
|
+
self._callback = None
|
|
272
|
+
|
|
273
|
+
def _request_next_frame(self) -> None:
|
|
274
|
+
self._request_id = requestAnimationFrame(self._callback)
|
|
275
|
+
|
|
276
|
+
def _on_animation_frame(self, timestamp: float) -> None:
|
|
277
|
+
if not self._running:
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
now = timestamp / 1000.0
|
|
281
|
+
dt = 0.0 if self._last_timestamp is None else now - self._last_timestamp
|
|
282
|
+
self._last_timestamp = now
|
|
283
|
+
self._request_id = None
|
|
284
|
+
self._event_loop._redraw_windows(dt) # noqa: SLF001
|
|
285
|
+
|
|
286
|
+
if self._running:
|
|
287
|
+
self._request_next_frame()
|