pyglet 2.1.13__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 +5 -21
- 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 +28 -8
- 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 +154 -194
- 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.13.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
- pyglet-3.0.dev1.dist-info/RECORD +322 -0
- {pyglet-2.1.13.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.13.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.13.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from pyglet.libs.darwin import ObjCClass, ns_to_py, nsstr_to_py, AutoReleasePool, pystr_to_ns, py_to_ns
|
|
6
|
+
from pyglet.libs.darwin.cocoapy.runtime import get_callback_block, ObjCBlock
|
|
7
|
+
from pyglet.window.dialog.base import FileOpenDialogBase, FileSaveDialogBase
|
|
8
|
+
|
|
9
|
+
NSOpenPanel = ObjCClass('NSOpenPanel')
|
|
10
|
+
NSSavePanel = ObjCClass('NSSavePanel')
|
|
11
|
+
NSURL = ObjCClass('NSURL')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MacOSFileOpenDialog(FileOpenDialogBase):
|
|
15
|
+
_cb_block: ObjCBlock | None = None
|
|
16
|
+
def __init__(
|
|
17
|
+
self, title: str="Open File", initial_dir: str | Path=Path.cwd(), initial_file: str | None=None,
|
|
18
|
+
filetypes: list[tuple[str, str]] | None=None, multiple: bool=False) -> None:
|
|
19
|
+
super().__init__(title, initial_dir, initial_file, filetypes, multiple)
|
|
20
|
+
self.blocking = False
|
|
21
|
+
|
|
22
|
+
def _dispatch_event(self, files):
|
|
23
|
+
self.dispatch_event("on_dialog_open", files)
|
|
24
|
+
|
|
25
|
+
def open(self):
|
|
26
|
+
"""Initial_file is not supported on Mac."""
|
|
27
|
+
with AutoReleasePool():
|
|
28
|
+
url = Path(self.initial_dir).as_posix()
|
|
29
|
+
nsurl_path = NSURL.fileURLWithPath_(pystr_to_ns(url))
|
|
30
|
+
panel = NSOpenPanel.openPanel()
|
|
31
|
+
panel.setAllowsMultipleSelection_(self.multiple)
|
|
32
|
+
panel.setDirectoryURL_(nsurl_path)
|
|
33
|
+
panel.setMessage_(pystr_to_ns(self.title))
|
|
34
|
+
|
|
35
|
+
if self.filetypes:
|
|
36
|
+
allowed = []
|
|
37
|
+
for name, exts in self.filetypes:
|
|
38
|
+
for ext in exts.split(" "):
|
|
39
|
+
strip_endings = ext.lstrip("*").lstrip(".")
|
|
40
|
+
allowed.append(pystr_to_ns(strip_endings)) # mac wants extension without "."
|
|
41
|
+
panel.setAllowedFileTypes_(py_to_ns(allowed))
|
|
42
|
+
|
|
43
|
+
if self.blocking:
|
|
44
|
+
result = panel.runModal()
|
|
45
|
+
|
|
46
|
+
if result == 1:
|
|
47
|
+
files = [nsstr_to_py(_url.path()) for _url in ns_to_py(panel.URLs())]
|
|
48
|
+
else:
|
|
49
|
+
files = []
|
|
50
|
+
self._dispatch_event(files)
|
|
51
|
+
else:
|
|
52
|
+
if MacOSFileOpenDialog._cb_block is not None:
|
|
53
|
+
raise Exception("An existing dialog is already open.")
|
|
54
|
+
|
|
55
|
+
def handler(_result: int):
|
|
56
|
+
try:
|
|
57
|
+
if _result == 1:
|
|
58
|
+
files = [nsstr_to_py(_url.path()) for _url in ns_to_py(panel.URLs())]
|
|
59
|
+
else:
|
|
60
|
+
files = []
|
|
61
|
+
self._dispatch_event(files)
|
|
62
|
+
finally:
|
|
63
|
+
MacOSFileOpenDialog._cb_block = None
|
|
64
|
+
|
|
65
|
+
MacOSFileOpenDialog._cb_block = get_callback_block(handler, encoding=[b"v", b"l"])
|
|
66
|
+
panel.beginWithCompletionHandler_(MacOSFileOpenDialog._cb_block)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class MacOSFileSaveDialog(FileSaveDialogBase):
|
|
70
|
+
_cb_block = None
|
|
71
|
+
def __init__(self, title="Save As", initial_dir=Path.cwd(), initial_file=None, filetypes=None, default_ext=""):
|
|
72
|
+
super().__init__(title, initial_dir, initial_file, filetypes, default_ext)
|
|
73
|
+
self.blocking = False
|
|
74
|
+
|
|
75
|
+
def open(self):
|
|
76
|
+
"""Initial_file is not supported on Mac."""
|
|
77
|
+
with AutoReleasePool():
|
|
78
|
+
url = Path(self.initial_dir).as_posix()
|
|
79
|
+
nsurl_path = NSURL.fileURLWithPath_(pystr_to_ns(url))
|
|
80
|
+
panel = NSSavePanel.savePanel()
|
|
81
|
+
panel.setDirectoryURL_(nsurl_path)
|
|
82
|
+
panel.setMessage_(pystr_to_ns(self.title))
|
|
83
|
+
if self.initial_file:
|
|
84
|
+
panel.setNameFieldStringValue_(pystr_to_ns(self.initial_file))
|
|
85
|
+
|
|
86
|
+
if self.filetypes:
|
|
87
|
+
allowed = []
|
|
88
|
+
for name, exts in self.filetypes:
|
|
89
|
+
for ext in exts.split(" "):
|
|
90
|
+
strip_endings = ext.lstrip("*").lstrip(".")
|
|
91
|
+
allowed.append(pystr_to_ns(strip_endings)) # mac wants extension without "."
|
|
92
|
+
panel.setAllowedFileTypes_(py_to_ns(allowed))
|
|
93
|
+
else:
|
|
94
|
+
panel.setAllowedFileTypes_(py_to_ns([pystr_to_ns(self.default_ext)]))
|
|
95
|
+
panel.setAllowsOtherFileTypes_(True)
|
|
96
|
+
panel.setShowsContentTypes_(True)
|
|
97
|
+
if self.blocking:
|
|
98
|
+
result = panel.runModal()
|
|
99
|
+
|
|
100
|
+
if result == 1:
|
|
101
|
+
self._dispatch_event(nsstr_to_py(panel.URL().path()))
|
|
102
|
+
else: # cancelled
|
|
103
|
+
self._dispatch_event("")
|
|
104
|
+
else:
|
|
105
|
+
if MacOSFileSaveDialog._cb_block is not None:
|
|
106
|
+
raise Exception("An existing dialog is already open.")
|
|
107
|
+
|
|
108
|
+
def handler(_result: int):
|
|
109
|
+
try:
|
|
110
|
+
if _result == 1:
|
|
111
|
+
self._dispatch_event(nsstr_to_py(panel.URL().path()))
|
|
112
|
+
else: # cancelled
|
|
113
|
+
self._dispatch_event([])
|
|
114
|
+
finally:
|
|
115
|
+
MacOSFileSaveDialog._cb_block = None
|
|
116
|
+
|
|
117
|
+
MacOSFileSaveDialog._cb_block = get_callback_block(handler, encoding=[b"v", b"l"])
|
|
118
|
+
panel.beginWithCompletionHandler_(MacOSFileSaveDialog._cb_block)
|
|
119
|
+
|
|
120
|
+
def _dispatch_event(self, filename: str):
|
|
121
|
+
self.dispatch_event("on_dialog_save", filename)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Loads a file dialog utilizing TKinter.
|
|
2
|
+
|
|
3
|
+
Linux does not have any unified dialog API or utility across distros.
|
|
4
|
+
|
|
5
|
+
While many support a GTK or KDialog, it's not guaranteed. Because of this, TKinter is utilized
|
|
6
|
+
as the main caller. When Tkinter cannot create a system dialog, it will create a custom
|
|
7
|
+
created file explorer, thus guaranteeing a dialog on any Linux system.
|
|
8
|
+
|
|
9
|
+
This is done in a separate process as Tk has its own event loop.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from concurrent.futures import ProcessPoolExecutor as _ProcessPoolExecutor
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import pyglet
|
|
17
|
+
from pyglet.window.dialog.base import FileOpenDialogBase, FileSaveDialogBase
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _TkFileDialogBackend:
|
|
21
|
+
|
|
22
|
+
executor = _ProcessPoolExecutor(max_workers=1)
|
|
23
|
+
_dialog = None
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _open_dialog(dialog):
|
|
27
|
+
import tkinter as tk
|
|
28
|
+
root = tk.Tk()
|
|
29
|
+
root.withdraw()
|
|
30
|
+
try:
|
|
31
|
+
return dialog.show()
|
|
32
|
+
finally:
|
|
33
|
+
root.destroy()
|
|
34
|
+
|
|
35
|
+
def open(self):
|
|
36
|
+
future = self.executor.submit(self._open_dialog, self._dialog)
|
|
37
|
+
future.add_done_callback(self._dispatch_event)
|
|
38
|
+
|
|
39
|
+
def _dispatch_event(self, future):
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
class TkFileOpenDialog(_TkFileDialogBackend, FileOpenDialogBase):
|
|
43
|
+
def __init__(
|
|
44
|
+
self, title: str="Open File", initial_dir: str | Path=Path.cwd(), initial_file: str | None=None,
|
|
45
|
+
filetypes: list[tuple[str, str]] | None=None, multiple: bool=False) -> None:
|
|
46
|
+
super().__init__(title, initial_dir, initial_file, filetypes, multiple)
|
|
47
|
+
from tkinter import filedialog
|
|
48
|
+
self._dialog = filedialog.Open(title=title,
|
|
49
|
+
initialdir=initial_dir,
|
|
50
|
+
initialfile=initial_file,
|
|
51
|
+
filetypes=filetypes or (),
|
|
52
|
+
multiple=multiple)
|
|
53
|
+
|
|
54
|
+
def _dispatch_event(self, future):
|
|
55
|
+
pyglet.app.platform_event_loop.post_event(self, "on_dialog_open", future.result())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TkFileSaveDialog(_TkFileDialogBackend, FileSaveDialogBase):
|
|
59
|
+
def __init__(self, title="Save As", initial_dir=Path.cwd(), initial_file=None, filetypes=None, default_ext=""):
|
|
60
|
+
super().__init__(title, initial_dir, initial_file, filetypes, default_ext)
|
|
61
|
+
from tkinter import filedialog
|
|
62
|
+
|
|
63
|
+
self._dialog = filedialog.SaveAs(
|
|
64
|
+
title=title,
|
|
65
|
+
initialdir=initial_dir,
|
|
66
|
+
initialfile=initial_file or (),
|
|
67
|
+
filetypes=filetypes or (),
|
|
68
|
+
defaultextension=default_ext,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _dispatch_event(self, future):
|
|
72
|
+
pyglet.app.platform_event_loop.post_event(self, "on_dialog_save", future.result())
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import threading
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from _ctypes import sizeof
|
|
8
|
+
from ctypes import create_unicode_buffer
|
|
9
|
+
|
|
10
|
+
import pyglet
|
|
11
|
+
from pyglet.libs.win32 import OPENFILENAMEW, _comdlg32
|
|
12
|
+
from pyglet.libs.win32.constants import (
|
|
13
|
+
OFN_EXPLORER,
|
|
14
|
+
OFN_PATHMUSTEXIST,
|
|
15
|
+
OFN_OVERWRITEPROMPT,
|
|
16
|
+
OFN_FILEMUSTEXIST,
|
|
17
|
+
OFN_ALLOWMULTISELECT,
|
|
18
|
+
)
|
|
19
|
+
from pyglet.window.dialog.base import FileOpenDialogBase, FileSaveDialogBase
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _build_filter_string(filetypes: list[tuple[str, str]]) -> str:
|
|
23
|
+
"""Create filter string for filetypes.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
filetypes:
|
|
27
|
+
e.g. [("PNG", "*.png"), ("24-bit Bitmap", "*.bmp"), ("Images", "*.png;*.bmp"]
|
|
28
|
+
"""
|
|
29
|
+
parts: list[str] = []
|
|
30
|
+
|
|
31
|
+
for label, exts in filetypes:
|
|
32
|
+
# Normalize to a list
|
|
33
|
+
if isinstance(exts, str):
|
|
34
|
+
exts = [exts]
|
|
35
|
+
|
|
36
|
+
norm_patterns: list[str] = []
|
|
37
|
+
for ext in exts:
|
|
38
|
+
ext = ext.strip()
|
|
39
|
+
if not ext:
|
|
40
|
+
continue
|
|
41
|
+
if ext.startswith("*."):
|
|
42
|
+
pass # already "*.png"
|
|
43
|
+
elif ext.startswith("."): # Just incase, add * if someone didn't add.
|
|
44
|
+
ext = "*" + ext # ".png" -> "*.png"
|
|
45
|
+
else:
|
|
46
|
+
ext = "*." + ext # "png" -> "*.png"
|
|
47
|
+
norm_patterns.append(ext)
|
|
48
|
+
|
|
49
|
+
if not norm_patterns:
|
|
50
|
+
pattern_str = "*.*"
|
|
51
|
+
else:
|
|
52
|
+
pattern_str = ";".join(norm_patterns)
|
|
53
|
+
|
|
54
|
+
# Add pattern to the label.
|
|
55
|
+
display_label = f"{label} ({pattern_str})"
|
|
56
|
+
|
|
57
|
+
parts.append(display_label)
|
|
58
|
+
parts.append(pattern_str)
|
|
59
|
+
|
|
60
|
+
# Join pairs with single nulls and end with double null
|
|
61
|
+
return "\0".join(parts) + "\0\0"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _parse_multiselect(buffer) -> list[str]:
|
|
65
|
+
text = buffer[:]
|
|
66
|
+
|
|
67
|
+
# Split on nulls
|
|
68
|
+
parts = text.split("\0")
|
|
69
|
+
|
|
70
|
+
# Remove empty entries
|
|
71
|
+
parts = [p for p in parts if p]
|
|
72
|
+
|
|
73
|
+
if not parts:
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
# If only 1 part → it's a single file
|
|
77
|
+
if len(parts) == 1:
|
|
78
|
+
return [parts[0]]
|
|
79
|
+
|
|
80
|
+
# otherwise:
|
|
81
|
+
folder = parts[0]
|
|
82
|
+
files = parts[1:]
|
|
83
|
+
|
|
84
|
+
return [folder + "\\" + f for f in files]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _build_ofn(hwnd, title, initialdir, initial_file, filetypes, multiple, for_save=False, buffer_chars=65536, default_ext=None,
|
|
88
|
+
):
|
|
89
|
+
file_buffer = create_unicode_buffer(buffer_chars)
|
|
90
|
+
|
|
91
|
+
if initial_file:
|
|
92
|
+
file_buffer.value = initial_file
|
|
93
|
+
|
|
94
|
+
filter_str = _build_filter_string(filetypes) if filetypes else None
|
|
95
|
+
|
|
96
|
+
ofn = OPENFILENAMEW()
|
|
97
|
+
ofn.lStructSize = sizeof(ofn)
|
|
98
|
+
ofn.hwndOwner = hwnd
|
|
99
|
+
ofn.lpstrFilter = filter_str
|
|
100
|
+
ofn.nFilterIndex = 1
|
|
101
|
+
|
|
102
|
+
ofn.lpstrFile = ctypes.cast(file_buffer, ctypes.c_wchar_p)
|
|
103
|
+
ofn.nMaxFile = buffer_chars
|
|
104
|
+
|
|
105
|
+
# Optional fields
|
|
106
|
+
ofn.lpstrFileTitle = None
|
|
107
|
+
ofn.nMaxFileTitle = 0
|
|
108
|
+
if isinstance(initialdir, Path):
|
|
109
|
+
initialdir = initialdir.absolute()
|
|
110
|
+
ofn.lpstrInitialDir = str(initialdir)
|
|
111
|
+
ofn.lpstrTitle = title
|
|
112
|
+
|
|
113
|
+
# Base flags
|
|
114
|
+
flags = OFN_EXPLORER
|
|
115
|
+
|
|
116
|
+
if for_save:
|
|
117
|
+
flags |= OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT
|
|
118
|
+
else:
|
|
119
|
+
flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
|
|
120
|
+
|
|
121
|
+
if multiple and not for_save:
|
|
122
|
+
flags |= OFN_ALLOWMULTISELECT
|
|
123
|
+
|
|
124
|
+
ofn.Flags = flags
|
|
125
|
+
|
|
126
|
+
# Default extension for save dialog (optional)
|
|
127
|
+
if for_save and default_ext:
|
|
128
|
+
# strip leading dot
|
|
129
|
+
if default_ext.startswith("."):
|
|
130
|
+
default_ext = default_ext[1:]
|
|
131
|
+
ofn.lpstrDefExt = default_ext
|
|
132
|
+
|
|
133
|
+
return ofn, file_buffer
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class WindowsFileOpenDialog(FileOpenDialogBase):
|
|
137
|
+
|
|
138
|
+
def _open_dialog(self) -> list[str] | None:
|
|
139
|
+
ofn, file_buffer = _build_ofn(
|
|
140
|
+
hwnd=None,
|
|
141
|
+
title=self.title,
|
|
142
|
+
initialdir=self.initial_dir,
|
|
143
|
+
initial_file=self.initial_file,
|
|
144
|
+
filetypes=self.filetypes,
|
|
145
|
+
multiple=self.multiple,
|
|
146
|
+
for_save=False,
|
|
147
|
+
buffer_chars=65536,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if not _comdlg32.GetOpenFileNameW(ctypes.byref(ofn)):
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
if self.multiple:
|
|
154
|
+
return _parse_multiselect(file_buffer.value)
|
|
155
|
+
|
|
156
|
+
return file_buffer.value
|
|
157
|
+
|
|
158
|
+
def open(self) -> None:
|
|
159
|
+
def run() -> None:
|
|
160
|
+
result = self._open_dialog()
|
|
161
|
+
if result is None:
|
|
162
|
+
result = []
|
|
163
|
+
assert isinstance(result, list)
|
|
164
|
+
pyglet.app.platform_event_loop.post_event(self, "on_dialog_open", result)
|
|
165
|
+
|
|
166
|
+
threading.Thread(target=run, daemon=True).start()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class WindowsFileSaveDialog(FileSaveDialogBase):
|
|
171
|
+
def _open_dialog(self) -> str:
|
|
172
|
+
ofn, file_buffer = _build_ofn(
|
|
173
|
+
hwnd=None,
|
|
174
|
+
title=self.title,
|
|
175
|
+
initialdir=self.initial_dir,
|
|
176
|
+
initial_file=self.initial_file,
|
|
177
|
+
filetypes=self.filetypes,
|
|
178
|
+
multiple=False,
|
|
179
|
+
for_save=True,
|
|
180
|
+
buffer_chars=65536,
|
|
181
|
+
default_ext=self.default_ext,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if not _comdlg32.GetSaveFileNameW(ctypes.byref(ofn)):
|
|
185
|
+
return ""
|
|
186
|
+
|
|
187
|
+
return file_buffer.value
|
|
188
|
+
|
|
189
|
+
def open(self) -> None:
|
|
190
|
+
def run() -> None:
|
|
191
|
+
result = self._open_dialog()
|
|
192
|
+
pyglet.app.platform_event_loop.post_event(self, "on_dialog_save", result)
|
|
193
|
+
|
|
194
|
+
threading.Thread(target=run, daemon=True).start()
|