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.
Files changed (267) hide show
  1. pyglet/__init__.py +67 -61
  2. pyglet/__init__.pyi +15 -8
  3. pyglet/app/__init__.py +22 -13
  4. pyglet/app/async_app.py +212 -0
  5. pyglet/app/base.py +2 -1
  6. pyglet/app/{xlib.py → linux.py} +3 -3
  7. pyglet/config/__init__.py +101 -0
  8. pyglet/config/gl/__init__.py +30 -0
  9. pyglet/config/gl/egl.py +120 -0
  10. pyglet/config/gl/macos.py +262 -0
  11. pyglet/config/gl/windows.py +267 -0
  12. pyglet/config/gl/x11.py +142 -0
  13. pyglet/customtypes.py +43 -2
  14. pyglet/display/__init__.py +8 -6
  15. pyglet/display/base.py +3 -63
  16. pyglet/display/cocoa.py +12 -17
  17. pyglet/display/emscripten.py +39 -0
  18. pyglet/display/headless.py +23 -30
  19. pyglet/display/wayland.py +157 -0
  20. pyglet/display/win32.py +5 -21
  21. pyglet/display/xlib.py +19 -27
  22. pyglet/display/xlib_vidmoderestore.py +2 -2
  23. pyglet/enums.py +183 -0
  24. pyglet/event.py +0 -1
  25. pyglet/experimental/geoshader_sprite.py +15 -13
  26. pyglet/experimental/hidraw.py +6 -15
  27. pyglet/experimental/multitexture_sprite.py +31 -19
  28. pyglet/experimental/particles.py +13 -35
  29. pyglet/font/__init__.py +251 -85
  30. pyglet/font/base.py +116 -61
  31. pyglet/font/dwrite/__init__.py +349 -204
  32. pyglet/font/dwrite/dwrite_lib.py +27 -5
  33. pyglet/font/fontconfig.py +14 -6
  34. pyglet/font/freetype.py +138 -87
  35. pyglet/font/freetype_lib.py +19 -0
  36. pyglet/font/group.py +179 -0
  37. pyglet/font/harfbuzz/__init__.py +3 -3
  38. pyglet/font/pyodide_js.py +310 -0
  39. pyglet/font/quartz.py +319 -126
  40. pyglet/font/ttf.py +45 -3
  41. pyglet/font/user.py +14 -19
  42. pyglet/font/win32.py +45 -21
  43. pyglet/graphics/__init__.py +8 -787
  44. pyglet/graphics/allocation.py +115 -1
  45. pyglet/graphics/api/__init__.py +77 -0
  46. pyglet/graphics/api/base.py +299 -0
  47. pyglet/graphics/api/gl/__init__.py +58 -0
  48. pyglet/graphics/api/gl/base.py +24 -0
  49. pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
  50. pyglet/graphics/api/gl/cocoa/context.py +76 -0
  51. pyglet/graphics/api/gl/context.py +391 -0
  52. pyglet/graphics/api/gl/default_shaders.py +0 -0
  53. pyglet/graphics/api/gl/draw.py +627 -0
  54. pyglet/graphics/api/gl/egl/__init__.py +0 -0
  55. pyglet/graphics/api/gl/egl/context.py +92 -0
  56. pyglet/graphics/api/gl/enums.py +76 -0
  57. pyglet/graphics/api/gl/framebuffer.py +315 -0
  58. pyglet/graphics/api/gl/gl.py +5463 -0
  59. pyglet/graphics/api/gl/gl_info.py +188 -0
  60. pyglet/graphics/api/gl/global_opengl.py +226 -0
  61. pyglet/{gl → graphics/api/gl}/lib.py +34 -18
  62. pyglet/graphics/api/gl/shader.py +1476 -0
  63. pyglet/graphics/api/gl/shapes.py +55 -0
  64. pyglet/graphics/api/gl/sprite.py +102 -0
  65. pyglet/graphics/api/gl/state.py +219 -0
  66. pyglet/graphics/api/gl/text.py +190 -0
  67. pyglet/graphics/api/gl/texture.py +1526 -0
  68. pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
  69. pyglet/graphics/api/gl/vertexdomain.py +751 -0
  70. pyglet/graphics/api/gl/win32/__init__.py +0 -0
  71. pyglet/graphics/api/gl/win32/context.py +108 -0
  72. pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
  73. pyglet/graphics/api/gl/xlib/__init__.py +0 -0
  74. pyglet/graphics/api/gl/xlib/context.py +174 -0
  75. pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
  76. pyglet/graphics/api/gl1/__init__.py +0 -0
  77. pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
  78. pyglet/graphics/api/gl2/__init__.py +0 -0
  79. pyglet/graphics/api/gl2/buffer.py +320 -0
  80. pyglet/graphics/api/gl2/draw.py +600 -0
  81. pyglet/graphics/api/gl2/global_opengl.py +122 -0
  82. pyglet/graphics/api/gl2/shader.py +200 -0
  83. pyglet/graphics/api/gl2/shapes.py +51 -0
  84. pyglet/graphics/api/gl2/sprite.py +79 -0
  85. pyglet/graphics/api/gl2/text.py +175 -0
  86. pyglet/graphics/api/gl2/vertexdomain.py +364 -0
  87. pyglet/graphics/api/webgl/__init__.py +233 -0
  88. pyglet/graphics/api/webgl/buffer.py +302 -0
  89. pyglet/graphics/api/webgl/context.py +234 -0
  90. pyglet/graphics/api/webgl/draw.py +590 -0
  91. pyglet/graphics/api/webgl/enums.py +76 -0
  92. pyglet/graphics/api/webgl/framebuffer.py +360 -0
  93. pyglet/graphics/api/webgl/gl.py +1537 -0
  94. pyglet/graphics/api/webgl/gl_info.py +130 -0
  95. pyglet/graphics/api/webgl/shader.py +1346 -0
  96. pyglet/graphics/api/webgl/shapes.py +92 -0
  97. pyglet/graphics/api/webgl/sprite.py +102 -0
  98. pyglet/graphics/api/webgl/state.py +227 -0
  99. pyglet/graphics/api/webgl/text.py +187 -0
  100. pyglet/graphics/api/webgl/texture.py +1227 -0
  101. pyglet/graphics/api/webgl/vertexarray.py +54 -0
  102. pyglet/graphics/api/webgl/vertexdomain.py +616 -0
  103. pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
  104. pyglet/{image → graphics}/atlas.py +33 -32
  105. pyglet/graphics/base.py +10 -0
  106. pyglet/graphics/buffer.py +245 -0
  107. pyglet/graphics/draw.py +578 -0
  108. pyglet/graphics/framebuffer.py +26 -0
  109. pyglet/graphics/instance.py +178 -69
  110. pyglet/graphics/shader.py +267 -1553
  111. pyglet/graphics/state.py +83 -0
  112. pyglet/graphics/texture.py +703 -0
  113. pyglet/graphics/vertexdomain.py +695 -538
  114. pyglet/gui/ninepatch.py +10 -10
  115. pyglet/gui/widgets.py +120 -10
  116. pyglet/image/__init__.py +20 -1973
  117. pyglet/image/animation.py +12 -12
  118. pyglet/image/base.py +730 -0
  119. pyglet/image/codecs/__init__.py +9 -0
  120. pyglet/image/codecs/bmp.py +53 -30
  121. pyglet/image/codecs/dds.py +53 -31
  122. pyglet/image/codecs/gdiplus.py +38 -14
  123. pyglet/image/codecs/gdkpixbuf2.py +0 -2
  124. pyglet/image/codecs/js_image.py +99 -0
  125. pyglet/image/codecs/ktx2.py +161 -0
  126. pyglet/image/codecs/pil.py +1 -1
  127. pyglet/image/codecs/png.py +1 -1
  128. pyglet/image/codecs/wic.py +11 -2
  129. pyglet/info.py +26 -24
  130. pyglet/input/__init__.py +8 -0
  131. pyglet/input/base.py +163 -105
  132. pyglet/input/controller.py +13 -19
  133. pyglet/input/controller_db.py +39 -24
  134. pyglet/input/emscripten/__init__.py +18 -0
  135. pyglet/input/emscripten/gamepad_js.py +397 -0
  136. pyglet/input/linux/__init__.py +11 -5
  137. pyglet/input/linux/evdev.py +10 -11
  138. pyglet/input/linux/x11_xinput.py +2 -2
  139. pyglet/input/linux/x11_xinput_tablet.py +1 -1
  140. pyglet/input/macos/__init__.py +7 -2
  141. pyglet/input/macos/darwin_gc.py +559 -0
  142. pyglet/input/win32/__init__.py +1 -1
  143. pyglet/input/win32/directinput.py +34 -29
  144. pyglet/input/win32/xinput.py +11 -61
  145. pyglet/lib.py +3 -3
  146. pyglet/libs/__init__.py +1 -1
  147. pyglet/{gl → libs/darwin}/agl.py +1 -1
  148. pyglet/libs/darwin/cocoapy/__init__.py +2 -2
  149. pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
  150. pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
  151. pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
  152. pyglet/libs/darwin/cocoapy/runtime.py +81 -45
  153. pyglet/libs/darwin/coreaudio.py +4 -4
  154. pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
  155. pyglet/libs/darwin/quartzkey.py +1 -3
  156. pyglet/libs/egl/__init__.py +2 -0
  157. pyglet/libs/egl/egl_lib.py +576 -0
  158. pyglet/libs/egl/eglext.py +51 -5
  159. pyglet/libs/linux/__init__.py +0 -0
  160. pyglet/libs/linux/egl/__init__.py +0 -0
  161. pyglet/libs/linux/egl/eglext.py +22 -0
  162. pyglet/libs/linux/glx/__init__.py +0 -0
  163. pyglet/{gl → libs/linux/glx}/glx.py +13 -14
  164. pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
  165. pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
  166. pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
  167. pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
  168. pyglet/libs/linux/wayland/__init__.py +0 -0
  169. pyglet/libs/linux/wayland/client.py +1068 -0
  170. pyglet/libs/linux/wayland/lib_wayland.py +207 -0
  171. pyglet/libs/linux/wayland/wayland_egl.py +38 -0
  172. pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
  173. pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
  174. pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
  175. pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
  176. pyglet/libs/linux/x11/xrandr.py +0 -0
  177. pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
  178. pyglet/libs/shared/__init__.py +0 -0
  179. pyglet/libs/shared/spirv/__init__.py +0 -0
  180. pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
  181. pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
  182. pyglet/libs/win32/__init__.py +28 -8
  183. pyglet/libs/win32/constants.py +59 -48
  184. pyglet/libs/win32/context_managers.py +20 -3
  185. pyglet/libs/win32/dinput.py +105 -88
  186. pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
  187. pyglet/libs/win32/types.py +58 -23
  188. pyglet/{gl → libs/win32}/wgl.py +32 -25
  189. pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
  190. pyglet/media/__init__.py +9 -10
  191. pyglet/media/codecs/__init__.py +12 -1
  192. pyglet/media/codecs/base.py +99 -96
  193. pyglet/media/codecs/ffmpeg.py +2 -2
  194. pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
  195. pyglet/media/codecs/webaudio_pyodide.py +111 -0
  196. pyglet/media/drivers/__init__.py +9 -4
  197. pyglet/media/drivers/base.py +4 -4
  198. pyglet/media/drivers/openal/__init__.py +1 -1
  199. pyglet/media/drivers/openal/adaptation.py +3 -3
  200. pyglet/media/drivers/pulse/__init__.py +1 -1
  201. pyglet/media/drivers/pulse/adaptation.py +3 -3
  202. pyglet/media/drivers/pyodide_js/__init__.py +8 -0
  203. pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
  204. pyglet/media/drivers/xaudio2/adaptation.py +3 -3
  205. pyglet/media/player.py +276 -193
  206. pyglet/media/player_worker_thread.py +1 -1
  207. pyglet/model/__init__.py +39 -29
  208. pyglet/model/codecs/base.py +4 -4
  209. pyglet/model/codecs/gltf.py +3 -3
  210. pyglet/model/codecs/obj.py +71 -43
  211. pyglet/resource.py +129 -78
  212. pyglet/shapes.py +154 -194
  213. pyglet/sprite.py +47 -164
  214. pyglet/text/__init__.py +44 -54
  215. pyglet/text/caret.py +12 -7
  216. pyglet/text/document.py +19 -17
  217. pyglet/text/formats/html.py +2 -2
  218. pyglet/text/formats/structured.py +10 -40
  219. pyglet/text/layout/__init__.py +20 -13
  220. pyglet/text/layout/base.py +176 -287
  221. pyglet/text/layout/incremental.py +9 -10
  222. pyglet/text/layout/scrolling.py +7 -95
  223. pyglet/window/__init__.py +183 -172
  224. pyglet/window/cocoa/__init__.py +62 -51
  225. pyglet/window/cocoa/pyglet_delegate.py +2 -25
  226. pyglet/window/cocoa/pyglet_view.py +9 -8
  227. pyglet/window/dialog/__init__.py +184 -0
  228. pyglet/window/dialog/base.py +99 -0
  229. pyglet/window/dialog/darwin.py +121 -0
  230. pyglet/window/dialog/linux.py +72 -0
  231. pyglet/window/dialog/windows.py +194 -0
  232. pyglet/window/emscripten/__init__.py +779 -0
  233. pyglet/window/headless/__init__.py +44 -28
  234. pyglet/window/key.py +2 -0
  235. pyglet/window/mouse.py +2 -2
  236. pyglet/window/wayland/__init__.py +377 -0
  237. pyglet/window/win32/__init__.py +101 -46
  238. pyglet/window/xlib/__init__.py +104 -66
  239. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
  240. pyglet-3.0.dev1.dist-info/RECORD +322 -0
  241. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
  242. pyglet/gl/__init__.py +0 -208
  243. pyglet/gl/base.py +0 -499
  244. pyglet/gl/cocoa.py +0 -309
  245. pyglet/gl/gl.py +0 -4625
  246. pyglet/gl/gl.pyi +0 -2320
  247. pyglet/gl/gl_compat.pyi +0 -3097
  248. pyglet/gl/gl_info.py +0 -190
  249. pyglet/gl/headless.py +0 -166
  250. pyglet/gl/wgl_info.py +0 -36
  251. pyglet/gl/wglext_nv.py +0 -1096
  252. pyglet/gl/win32.py +0 -268
  253. pyglet/gl/xlib.py +0 -295
  254. pyglet/image/buffer.py +0 -274
  255. pyglet/image/codecs/s3tc.py +0 -354
  256. pyglet/libs/x11/xrandr.py +0 -166
  257. pyglet-2.1.13.dist-info/RECORD +0 -234
  258. /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
  259. /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
  260. /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
  261. /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
  262. /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
  263. /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
  264. /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
  265. /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
  266. /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
  267. {pyglet-2.1.13.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,779 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import lru_cache
4
+ from typing import TYPE_CHECKING, Callable
5
+
6
+ import js
7
+ from pyodide.ffi import create_proxy
8
+
9
+ import pyglet.app
10
+ from pyglet.window import BaseWindow, DefaultMouseCursor, ImageMouseCursor, MouseCursor, key, mouse
11
+
12
+ if TYPE_CHECKING:
13
+ from pyglet.display.base import Display, Screen, ScreenMode
14
+ from pyglet.graphics.api import GraphicsConfig
15
+ from pyglet.graphics.api.base import WindowGraphicsContext
16
+
17
+ # Keymap using `event.code`
18
+ _key_map = {
19
+ # Alpha
20
+ "a": key.A,
21
+ "b": key.B,
22
+ "c": key.C,
23
+ "d": key.D,
24
+ "e": key.E,
25
+ "f": key.F,
26
+ "g": key.G,
27
+ "h": key.H,
28
+ "i": key.I,
29
+ "j": key.J,
30
+ "k": key.K,
31
+ "l": key.L,
32
+ "m": key.M,
33
+ "n": key.N,
34
+ "o": key.O,
35
+ "p": key.P,
36
+ "q": key.Q,
37
+ "r": key.R,
38
+ "s": key.S,
39
+ "t": key.T,
40
+ "u": key.U,
41
+ "v": key.V,
42
+ "w": key.W,
43
+ "x": key.X,
44
+ "y": key.Y,
45
+ "z": key.Z,
46
+ "A": key.A,
47
+ "B": key.B,
48
+ "C": key.C,
49
+ "D": key.D,
50
+ "E": key.E,
51
+ "F": key.F,
52
+ "G": key.G,
53
+ "H": key.H,
54
+ "I": key.I,
55
+ "J": key.J,
56
+ "K": key.K,
57
+ "L": key.L,
58
+ "M": key.M,
59
+ "N": key.N,
60
+ "O": key.O,
61
+ "P": key.P,
62
+ "Q": key.Q,
63
+ "R": key.R,
64
+ "S": key.S,
65
+ "T": key.T,
66
+ "U": key.U,
67
+ "V": key.V,
68
+ "W": key.W,
69
+ "X": key.X,
70
+ "Y": key.Y,
71
+ "Z": key.Z,
72
+ # Numeric
73
+ "0": key._0,
74
+ "1": key._1,
75
+ "2": key._2,
76
+ "3": key._3,
77
+ "4": key._4,
78
+ "5": key._5,
79
+ "6": key._6,
80
+ "7": key._7,
81
+ "8": key._8,
82
+ "9": key._9,
83
+ # Whitespace and control keys
84
+ "Backspace": key.BACKSPACE,
85
+ "Tab": key.TAB,
86
+ "Enter": key.RETURN,
87
+ "Escape": key.ESCAPE,
88
+ " ": key.SPACE,
89
+ # Navication keys
90
+ "Insert": key.INSERT,
91
+ "Home": key.HOME,
92
+ "End": key.END,
93
+ "PageUp": key.PAGEUP,
94
+ "PageDown": key.PAGEDOWN,
95
+ "ArrowLeft": key.LEFT,
96
+ "ArrowUp": key.UP,
97
+ "ArrowRight": key.RIGHT,
98
+ "ArrowDown": key.DOWN,
99
+ # Modifier keys
100
+ "Shift": key.LSHIFT,
101
+ "ShiftLeft": key.LSHIFT,
102
+ "ShiftRight": key.RSHIFT,
103
+ "Control": key.LCTRL,
104
+ "ControlLeft": key.LCTRL,
105
+ "ControlRight": key.RCTRL,
106
+ "Alt": key.LALT,
107
+ "AltLeft": key.LALT,
108
+ "AltRight": key.RALT,
109
+ "Meta": key.LMETA,
110
+ "MetaLeft": key.LMETA,
111
+ "MetaRight": key.RMETA,
112
+ "Pause": key.PAUSE,
113
+ "PrintScreen": key.PRINT,
114
+ "ContextMenu": key.MENU,
115
+ # Locks
116
+ "CapsLock": key.CAPSLOCK,
117
+ "NumLock": key.NUMLOCK,
118
+ "ScrollLock": key.SCROLLLOCK,
119
+ # Function keys
120
+ "F1": key.F1,
121
+ "F2": key.F2,
122
+ "F3": key.F3,
123
+ "F4": key.F4,
124
+ "F5": key.F5,
125
+ "F6": key.F6,
126
+ "F7": key.F7,
127
+ "F8": key.F8,
128
+ "F9": key.F9,
129
+ "F10": key.F10,
130
+ "F11": key.F11,
131
+ "F12": key.F12,
132
+ "Numpad0": key.NUM_0,
133
+ "Numpad1": key.NUM_1,
134
+ "Numpad2": key.NUM_2,
135
+ "Numpad3": key.NUM_3,
136
+ "Numpad4": key.NUM_4,
137
+ "Numpad5": key.NUM_5,
138
+ "Numpad6": key.NUM_6,
139
+ "Numpad7": key.NUM_7,
140
+ "Numpad8": key.NUM_8,
141
+ "Numpad9": key.NUM_9,
142
+ "NumpadMultiply": key.NUM_MULTIPLY,
143
+ "NumpadAdd": key.NUM_ADD,
144
+ "NumpadSubtract": key.NUM_SUBTRACT,
145
+ "NumpadDecimal": key.NUM_DECIMAL,
146
+ "NumpadDivide": key.NUM_DIVIDE,
147
+ "NumpadEnter": key.NUM_ENTER,
148
+ "!": key.EXCLAMATION,
149
+ "@": key.AT,
150
+ "#": key.HASH,
151
+ "$": key.DOLLAR,
152
+ "%": key.PERCENT,
153
+ "^": key.ASCIICIRCUM,
154
+ "&": key.AMPERSAND,
155
+ "*": key.ASTERISK,
156
+ "(": key.PARENLEFT,
157
+ ")": key.PARENRIGHT,
158
+ "-": key.MINUS,
159
+ "_": key.UNDERSCORE,
160
+ "=": key.EQUAL,
161
+ "+": key.PLUS,
162
+ "[": key.BRACKETLEFT,
163
+ "]": key.BRACKETRIGHT,
164
+ "{": key.BRACELEFT,
165
+ "}": key.BRACERIGHT,
166
+ ";": key.SEMICOLON,
167
+ ":": key.COLON,
168
+ "'": key.APOSTROPHE,
169
+ "\"": key.DOUBLEQUOTE,
170
+ "\\": key.BACKSLASH,
171
+ "|": key.BAR,
172
+ ",": key.COMMA,
173
+ ".": key.PERIOD,
174
+ "/": key.SLASH,
175
+ "?": key.QUESTION,
176
+ "`": key.GRAVE,
177
+ "~": key.ASCIITILDE,
178
+ }
179
+
180
+ # symbol,ctrl -> motion mapping
181
+ _motion_map: dict[tuple[int, bool], int] = {
182
+ (key.UP, False): key.MOTION_UP,
183
+ (key.RIGHT, False): key.MOTION_RIGHT,
184
+ (key.DOWN, False): key.MOTION_DOWN,
185
+ (key.LEFT, False): key.MOTION_LEFT,
186
+ (key.RIGHT, True): key.MOTION_NEXT_WORD,
187
+ (key.LEFT, True): key.MOTION_PREVIOUS_WORD,
188
+ (key.HOME, False): key.MOTION_BEGINNING_OF_LINE,
189
+ (key.END, False): key.MOTION_END_OF_LINE,
190
+ (key.PAGEUP, False): key.MOTION_PREVIOUS_PAGE,
191
+ (key.PAGEDOWN, False): key.MOTION_NEXT_PAGE,
192
+ (key.HOME, True): key.MOTION_BEGINNING_OF_FILE,
193
+ (key.END, True): key.MOTION_END_OF_FILE,
194
+ (key.BACKSPACE, False): key.MOTION_BACKSPACE,
195
+ (key.DELETE, False): key.MOTION_DELETE,
196
+ (key.C, True): key.MOTION_COPY,
197
+ (key.V, True): key.MOTION_PASTE,
198
+ }
199
+
200
+
201
+ def get_modifiers(event) -> int:
202
+ """Extract modifier flags from a JavaScript event."""
203
+ modifiers = 0
204
+
205
+ if event.ctrlKey:
206
+ modifiers |= key.MOD_CTRL
207
+ if event.shiftKey:
208
+ modifiers |= key.MOD_SHIFT
209
+ if event.altKey:
210
+ modifiers |= key.MOD_ALT
211
+ modifiers |= key.MOD_OPTION # Alt is also "Option" on macOS
212
+ if hasattr(event, "metaKey") and event.metaKey: # Meta = Command on macOS
213
+ modifiers |= key.MOD_COMMAND
214
+
215
+ return modifiers
216
+
217
+
218
+ # Convert JavaScript key events to your format
219
+ def js_key_to_pyglet(event):
220
+ """Convert JS event code to the equivalent key mapping."""
221
+ modifiers = 0
222
+
223
+ if event.key in _key_map:
224
+ symbol = _key_map[event.key]
225
+ # Doesn't exist in either mapping. Unknown.
226
+ else:
227
+ symbol = event.code if event.code else 0
228
+ print(f"Warning: Unmapped key -> {symbol} ({event.key})")
229
+
230
+ # Handle modifiers
231
+ if event.ctrlKey:
232
+ modifiers |= key.MOD_CTRL
233
+ if event.shiftKey:
234
+ modifiers |= key.MOD_SHIFT
235
+ if event.altKey:
236
+ modifiers |= key.MOD_ALT
237
+ modifiers |= key.MOD_OPTION # Alt is also "Option" on macOS
238
+ if hasattr(event, "metaKey") and event.metaKey: # Meta = Command on macOS
239
+ modifiers |= key.MOD_COMMAND
240
+
241
+ return symbol, modifiers
242
+
243
+
244
+ # Javascript Buttons Bitwise
245
+ JS_MOUSE_LEFT_BIT = 1 << 0 # 1
246
+ JS_MOUSE_RIGHT_BIT = 1 << 1 # 2
247
+ JS_MOUSE_MIDDLE_BIT = 1 << 2 # 4
248
+ JS_MOUSE_BACK_BIT = 1 << 3 # 8
249
+ JS_MOUSE_FORWARD_BIT = 1 << 4 # 16
250
+
251
+ JS_MOUSE_LEFT = 0
252
+ JS_MOUSE_MIDDLE = 1
253
+ JS_MOUSE_RIGHT = 2
254
+ JS_MOUSE_BACK = 3 # Mouse 4
255
+ JS_MOUSE_FORWARD = 4 # Mouse 5
256
+
257
+ # Normal button presses are not bitwise.
258
+ _mouse_map = {
259
+ JS_MOUSE_LEFT: mouse.LEFT,
260
+ JS_MOUSE_MIDDLE: mouse.MIDDLE,
261
+ JS_MOUSE_RIGHT: mouse.RIGHT,
262
+ JS_MOUSE_BACK: mouse.MOUSE4,
263
+ JS_MOUSE_FORWARD: mouse.MOUSE5,
264
+ }
265
+
266
+
267
+ def translate_mouse_bits(buttons: int) -> int:
268
+ """Translate JavaScript mouse button values to match pyglet constants."""
269
+ result = 0
270
+ if buttons & JS_MOUSE_LEFT_BIT: # JavaScript Left button
271
+ result |= mouse.LEFT
272
+ if buttons & JS_MOUSE_RIGHT_BIT: # JavaScript Right button
273
+ result |= mouse.RIGHT
274
+ if buttons & JS_MOUSE_MIDDLE_BIT: # JavaScript Middle button (wheel)
275
+ result |= mouse.MIDDLE
276
+ if buttons & JS_MOUSE_BACK_BIT: # JavaScript Back (X1)
277
+ result |= mouse.MOUSE4
278
+ if buttons & JS_MOUSE_FORWARD_BIT: # JavaScript Forward (X2)
279
+ result |= mouse.MOUSE5
280
+ return result
281
+
282
+
283
+ def CanvasEventHandler(name: str, passive: None| bool=None) -> Callable:
284
+ def _event_wrapper(f: Callable) -> Callable:
285
+ f._canvas = True
286
+ f._passive = passive
287
+ f._platform_event = True # noqa: SLF001
288
+ if not hasattr(f, '_platform_event_data'):
289
+ f._platform_event_data = [] # noqa: SLF001
290
+ f._platform_event_data.append(name) # noqa: SLF001
291
+ return f
292
+
293
+ return _event_wrapper
294
+
295
+ # The Canvas JS object.
296
+ class HTMLCanvasElement:
297
+ ...
298
+
299
+ class JavascriptCursor(MouseCursor):
300
+ api_drawable: bool = False
301
+ hw_drawable: bool = True
302
+
303
+ def __init__(self, name: str):
304
+ self.name = name
305
+
306
+
307
+ # Temporary
308
+ class EmscriptenWindow(BaseWindow):
309
+ """The HTML5 Canvas."""
310
+
311
+ _mouse_cursor: JavascriptCursor
312
+
313
+ def __init__(
314
+ self,
315
+ width: int | None = None,
316
+ height: int | None = None,
317
+ caption: str | None = None,
318
+ resizable: bool = False,
319
+ style: str | None = None,
320
+ fullscreen: bool = False,
321
+ visible: bool = True,
322
+ vsync: bool = True,
323
+ file_drops: bool = False,
324
+ display: Display | None = None,
325
+ screen: Screen | None = None,
326
+ config: GraphicsConfig | None = None,
327
+ context: WindowGraphicsContext | None = None,
328
+ mode: ScreenMode | None = None,
329
+ ) -> None:
330
+ self._canvas = None
331
+ self._event_handlers: dict[int, Callable] = {}
332
+ self._canvas_event_handlers: dict[int, Callable] = {}
333
+ self._keys_down = set()
334
+
335
+ self._scale = js.window.devicePixelRatio
336
+ super().__init__(
337
+ width,
338
+ height,
339
+ caption,
340
+ resizable,
341
+ style,
342
+ fullscreen,
343
+ visible,
344
+ vsync,
345
+ file_drops,
346
+ display,
347
+ screen,
348
+ config,
349
+ context,
350
+ mode,
351
+ )
352
+ self._enable_event_queue = False
353
+
354
+ @property
355
+ def scale(self) -> float:
356
+ """The scale of the window factoring in DPI.
357
+
358
+ Read only.
359
+ """
360
+ return self._scale
361
+
362
+ @property
363
+ def dpi(self) -> int:
364
+ """DPI values of the Window.
365
+
366
+ Read only.
367
+ """
368
+ return int(self._scale * 96)
369
+
370
+ @property
371
+ def canvas(self) -> HTMLCanvasElement:
372
+ """The underlying Javascript Canvas element, if available.
373
+
374
+ Read only.
375
+ """
376
+ return self._canvas
377
+
378
+ def _set_event_handlers(self) -> None:
379
+ assert self._canvas, "Canvas has not been created."
380
+ for func_name in self._platform_event_names:
381
+ if not hasattr(self, func_name):
382
+ continue
383
+ func = getattr(self, func_name)
384
+ for message in func._platform_event_data: # noqa: SLF001
385
+ assert message not in self._event_handlers, f"event: '{message}' already exists."
386
+ proxy = create_proxy(func)
387
+ if hasattr(func, '_canvas'):
388
+ self._canvas_event_handlers[message] = func
389
+ if func._passive is not None:
390
+ # Not sure what behavior results if we pass None to the JS event handler.
391
+ self._canvas.addEventListener(message, proxy, passive=func._passive)
392
+ else:
393
+ self._canvas.addEventListener(message, proxy)
394
+ else:
395
+ self._event_handlers[message] = func
396
+ js.window.addEventListener(message, proxy)
397
+
398
+ self._proxy_resize = create_proxy(self._event_resized)
399
+ self._observer = js.ResizeObserver.new(self._proxy_resize)
400
+ self._observer.observe(self._canvas)
401
+
402
+ def set_fullscreen(
403
+ self,
404
+ fullscreen: bool = True,
405
+ _screen: Screen | None = None,
406
+ mode: ScreenMode | None = None,
407
+ width: int | None = None,
408
+ height: int | None = None,
409
+ ) -> None:
410
+ if (
411
+ fullscreen == self._fullscreen
412
+ and (_screen is None or _screen is self._screen)
413
+ and (width is None or width == self._width)
414
+ and (height is None or height == self._height)
415
+ ):
416
+ return
417
+
418
+ if not self._fullscreen:
419
+ self._windowed_size = self.get_size()
420
+
421
+ self._fullscreen = fullscreen
422
+ if self._fullscreen:
423
+ self._canvas.requestFullscreen()
424
+ else:
425
+ js.document.exitFullscreen()
426
+
427
+ def _enter_fullscreen(self, event):
428
+ self._fullscreen = True
429
+ scale = js.window.devicePixelRatio
430
+ self._canvas.width = js.window.innerWidth
431
+ self._canvas.height = js.window.innerHeight
432
+
433
+ def _exited_fullscreen(self, event, width: int | None = None, height: int | None = None):
434
+ self._fullscreen = False
435
+ self._width, self._height = self._windowed_size
436
+ self._canvas.width = self._width
437
+ self._canvas.height = self._height
438
+ if width is not None:
439
+ self._width = width
440
+ if height is not None:
441
+ self._height = height
442
+
443
+ def flip(self) -> None:
444
+ if self._context:
445
+ self._context.flip()
446
+
447
+ def switch_to(self) -> None:
448
+ pass
449
+
450
+ def before_draw(self) -> None:
451
+ if self._context:
452
+ self._context.before_draw()
453
+
454
+ def set_caption(self, caption: str) -> None:
455
+ js.document.title = caption
456
+
457
+ def set_minimum_size(self, width: int | str, height: int | str) -> None:
458
+ self._minimum_size = width, height
459
+ if isinstance(width, int) or isinstance(width, float):
460
+ width = f"{width}px"
461
+
462
+ if isinstance(width, int) or isinstance(width, float):
463
+ height = f"{width}px"
464
+
465
+ self._canvas.style.minWidth = width
466
+ self._canvas.style.minHeight = height
467
+
468
+ def set_maximum_size(self, width: int | str, height: int | str) -> None:
469
+ self._maximum_size = width, height
470
+ if isinstance(width, int) or isinstance(width, float):
471
+ width = f"{width}px"
472
+
473
+ if isinstance(width, int) or isinstance(width, float):
474
+ height = f"{width}px"
475
+
476
+ self._canvas.style.maxWidth = width
477
+ self._canvas.style.maxHeight = height
478
+
479
+ def set_size(self, width: int, height: int) -> None:
480
+ super().set_size(width, height)
481
+ self.adjust_scale(width, height)
482
+
483
+ def get_size(self) -> tuple[int, int]:
484
+ if not self._canvas:
485
+ return self._width, self._height
486
+
487
+ return self._canvas.width, self._canvas.height
488
+
489
+ def set_location(self, x: int, y: int) -> None:
490
+ # self.canvas.style.setProperty("position", "absolute")
491
+ self._canvas.style.setProperty("left", f"{x}px")
492
+ self._canvas.style.setProperty("top", f"{x}px")
493
+
494
+ def get_location(self) -> tuple[int, int]:
495
+ rect = self._canvas.getBoundingClientRect()
496
+ return rect.left, rect.top
497
+
498
+ def activate(self) -> None:
499
+ self._canvas.focus()
500
+
501
+ def set_visible(self, visible: bool = True) -> None:
502
+ if visible is False:
503
+ if self._canvas.style.visibility != "hidden":
504
+ self._canvas.visibility = "hidden"
505
+ else:
506
+ if self._canvas.style.visibility != "visible":
507
+ self._canvas.visibility = "visible"
508
+
509
+ def minimize(self) -> None:
510
+ """While minimized, events will not occur."""
511
+ self._canvas.style.display = "hidden"
512
+
513
+ def maximize(self) -> None:
514
+ self._canvas.style.display = "block"
515
+
516
+ def set_vsync(self, vsync: bool) -> None:
517
+ """A browser does not allow this."""
518
+
519
+ @lru_cache # noqa: B019
520
+ def _create_data_url_from_image(self, cursor: ImageMouseCursor) -> str:
521
+ """Create a cursor image from the data.
522
+
523
+ Use a DATA URI as this allows converting the image into a cursor without fetching through HTTP.
524
+
525
+ This does need to be in a PNG encoded format, so a temporary canvas is created to encode.
526
+ """
527
+ image = cursor.texture.get_image_data()
528
+ canvas = js.document.createElement("canvas")
529
+
530
+ canvas.width = image.width
531
+ canvas.height = image.height
532
+ data = image.get_bytes('RGBA')
533
+ pixel_array = js.Uint8ClampedArray.new(data)
534
+
535
+ ctx = canvas.getContext("2d")
536
+ image_data = ctx.createImageData(image.width, image.height)
537
+ image_data.data.set(pixel_array)
538
+ ctx.putImageData(image_data, 0, 0)
539
+
540
+ data_url = canvas.toDataURL("image/png")
541
+ canvas.remove()
542
+ return data_url
543
+
544
+ def set_mouse_cursor_platform_visible(self, platform_visible: bool | None = None) -> None:
545
+ if not self._canvas:
546
+ return
547
+
548
+ if platform_visible is None:
549
+ platform_visible = self._mouse_visible and (
550
+ not self._mouse_cursor.api_drawable or self._mouse_cursor.hw_drawable
551
+ )
552
+
553
+ if platform_visible is False:
554
+ cursor_name = "none"
555
+ elif isinstance(self._mouse_cursor, ImageMouseCursor) and self._mouse_cursor.hw_drawable:
556
+ # Create a custom hardware cursor.
557
+ cursor_name = self._create_data_url_from_image(self._mouse_cursor)
558
+ cursor_name = f"url({cursor_name}) {self._mouse_cursor.hot_x} {self._mouse_cursor.hot_y}, default"
559
+ else:
560
+ # Restore a standard hardware cursor
561
+ if isinstance(self._mouse_cursor, DefaultMouseCursor):
562
+ cursor_name = "default"
563
+ elif isinstance(self._mouse_cursor, JavascriptCursor):
564
+ cursor_name = self._mouse_cursor.name
565
+
566
+ self._canvas.style.cursor = cursor_name
567
+
568
+ def set_exclusive_mouse(self, exclusive: bool = True) -> None:
569
+ assert self._canvas
570
+ # Requires a user gesture to lock it like a button.
571
+ if exclusive is True:
572
+ self._canvas.requestPointerLock()
573
+ else:
574
+ self._canvas.exitPointerLock()
575
+
576
+ def set_exclusive_keyboard(self, exclusive: bool = True) -> None:
577
+ """Not relevant."""
578
+
579
+ def get_system_mouse_cursor(self, name: str) -> JavascriptCursor:
580
+ if name == self.CURSOR_DEFAULT:
581
+ return DefaultMouseCursor()
582
+
583
+ return JavascriptCursor(name)
584
+
585
+ async def dispatch_events(self) -> None:
586
+ """Process input events asynchronously."""
587
+ raise Exception("Not implemented.")
588
+
589
+ @staticmethod
590
+ def _event_text_motion(symbol: int, modifiers: int) -> int | None:
591
+ if modifiers & key.MOD_ALT:
592
+ return None
593
+ ctrl = modifiers & key.MOD_CTRL != 0
594
+ return _motion_map.get((symbol, ctrl), None)
595
+
596
+ @CanvasEventHandler("keydown")
597
+ def _event_key_down(self, event):
598
+ if not event.repeat:
599
+ text = None
600
+ if len(event.key) == 1:
601
+ text = event.key
602
+ symbol, modifiers = js_key_to_pyglet(event)
603
+ motion = self._event_text_motion(symbol, modifiers)
604
+ modifiers_ctrl = modifiers & (key.MOD_CTRL | key.MOD_ALT)
605
+ # If key A is pressed, then key B is pressed, if key A is released, key B will trigger a keydown.
606
+ if symbol in self._keys_down:
607
+ return
608
+ if symbol:
609
+ self._keys_down.add(symbol) # Keep track of pressed internally.
610
+ self.dispatch_event('on_key_press', symbol, modifiers)
611
+ if motion:
612
+ if modifiers & key.MOD_SHIFT:
613
+ motion_event = 'on_text_motion_select'
614
+ else:
615
+ motion_event = 'on_text_motion'
616
+ self.dispatch_event(motion_event, motion)
617
+ elif text and not modifiers_ctrl:
618
+ self.dispatch_event('on_text', text)
619
+
620
+ @CanvasEventHandler("keyup")
621
+ def _event_key_up(self, event):
622
+ symbol, modifiers = js_key_to_pyglet(event)
623
+ if symbol in self._keys_down:
624
+ self._keys_down.remove(symbol)
625
+ else:
626
+ js.console.log(f"{event.key} was released but was not down. This should not occur.")
627
+
628
+ self.dispatch_event('on_key_release', symbol, modifiers)
629
+
630
+ @CanvasEventHandler("mousedown")
631
+ def _event_mouse_down(self, event):
632
+ rect = self._canvas.getBoundingClientRect()
633
+ modifiers = get_modifiers(event)
634
+ pos_x = (event.clientX - rect.left) * self._scale
635
+ pos_y = self._canvas.height - (event.clientY - rect.top) * self._scale
636
+ self.dispatch_event(
637
+ 'on_mouse_press', pos_x, pos_y, _mouse_map.get(event.button, 0), modifiers,
638
+ )
639
+
640
+ @CanvasEventHandler("mouseup")
641
+ def _event_mouse_up(self, event):
642
+ rect = self._canvas.getBoundingClientRect()
643
+ modifiers = get_modifiers(event)
644
+ pos_x = (event.clientX - rect.left) * self._scale
645
+ pos_y = self._canvas.height - (event.clientY - rect.top) * self._scale
646
+ self.dispatch_event(
647
+ 'on_mouse_release', pos_x, pos_y, _mouse_map.get(event.button, 0), modifiers,
648
+ )
649
+
650
+ @CanvasEventHandler("mousemove")
651
+ async def _event_mouse_motion(self, event):
652
+ rect = self._canvas.getBoundingClientRect()
653
+ if event.buttons:
654
+ modifiers = get_modifiers(event)
655
+ pos_x = (event.clientX - rect.left) * self._scale
656
+ pos_y = self._canvas.height - (event.clientY - rect.top) * self._scale
657
+ self.dispatch_event(
658
+ 'on_mouse_drag',
659
+ pos_x,
660
+ pos_y,
661
+ event.movementX,
662
+ event.movementY,
663
+ _mouse_map.get(event.button, 0),
664
+ modifiers,
665
+ )
666
+ else:
667
+ pos_x = (event.clientX - rect.left) * self._scale
668
+ pos_y = self._canvas.height - (event.clientY - rect.top) * self._scale
669
+ self.dispatch_event(
670
+ 'on_mouse_motion', pos_x, pos_y, event.movementX, event.movementY,
671
+ )
672
+
673
+ @CanvasEventHandler("wheel", passive=False)
674
+ def _event_mouse_scroll(self, event):
675
+ event.preventDefault()
676
+ self.dispatch_event('on_mouse_scroll', event.clientX, event.clientY, event.deltaX, event.deltaY)
677
+
678
+ @CanvasEventHandler("mouseenter")
679
+ async def _event_mouse_enter(self, event):
680
+ self.dispatch_event('on_mouse_enter', event.clientX, self.height - event.clientY)
681
+
682
+ @CanvasEventHandler("mouseleave")
683
+ async def _event_mouse_leave(self, event):
684
+ self.dispatch_event('on_mouse_leave', event.clientX, self.height - event.clientY)
685
+
686
+ @CanvasEventHandler("contextlost")
687
+ async def _event_context_lost(self, event):
688
+ print("WebGL context lost!")
689
+
690
+ @CanvasEventHandler("contextrestored")
691
+ async def _event_context_restored(self, event):
692
+ print("WebGL context restored!")
693
+
694
+ @CanvasEventHandler("focus")
695
+ async def _event_gain_focus(self, event):
696
+ await pyglet.app.platform_event_loop.post_event(self, 'on_activate')
697
+
698
+ @CanvasEventHandler("blur")
699
+ async def _event_lose_focus(self, event):
700
+ await pyglet.app.platform_event_loop.post_event(self, 'on_deactivate')
701
+
702
+ @CanvasEventHandler("contextmenu", passive=False)
703
+ def _event_contextmenu(self, event):
704
+ # Some keys or mouse presses (right click) can open the browser context menu. Disable this.
705
+ event.preventDefault()
706
+
707
+ @CanvasEventHandler("fullscreenchange")
708
+ def _event_fullscreen_change(self, event):
709
+ if js.document.fullscreenElement == self._canvas:
710
+ self._enter_fullscreen(event)
711
+ else:
712
+ self._exited_fullscreen(event, *self._windowed_size)
713
+
714
+ def get_framebuffer_size(self):
715
+ if self._context:
716
+ return self._context.gl.drawingBufferWidth, self._context.gl.drawingBufferHeight
717
+ return self.get_size()
718
+
719
+ def _event_resized(self, entries, observer):
720
+ for entry in entries:
721
+ rect = entry.contentRect
722
+ self.dispatch_event('_on_internal_resize', 0, 0)
723
+
724
+ def dispatch_pending_events(self) -> None:
725
+ pass
726
+
727
+ # copy, cut, paste, dragenter, dragleave,dragover, drop
728
+ # touchmove, touchstart, touchend, touchcancel
729
+
730
+ def adjust_scale(self, width: int, height: int) -> None:
731
+ ratio = js.window.devicePixelRatio or 1.0
732
+
733
+ # The framebuffer size.
734
+ self._canvas.width = width * ratio
735
+ self._canvas.height = height * ratio
736
+
737
+ # How large the canvas appears visually. Browser automatically scales based on DPI.
738
+ self._canvas.style.width = f"{width}px"
739
+ self._canvas.style.height = f"{height}px"
740
+
741
+ def _create(self) -> None:
742
+ assert self._canvas is None
743
+ canvas_name = pyglet.options.pyodide.canvas_id
744
+ canvas = js.document.getElementById(canvas_name)
745
+ if not self._canvas:
746
+ self._canvas = js.document.createElement("canvas")
747
+ self._canvas.id = canvas_name
748
+ js.document.body.appendChild(self._canvas)
749
+ else:
750
+ self._canvas = canvas
751
+
752
+ self._canvas.width = self._width * self.scale
753
+ self._canvas.height = self._height * self.scale
754
+ self._canvas.style.width = f"{self._width}px"
755
+ self._canvas.style.height = f"{self._height}px"
756
+
757
+ if not js.document.getElementById(canvas_name):
758
+ msg = f"Canvas: {canvas_name} could not be created."
759
+ raise Exception(msg)
760
+
761
+ # By default, the canvas does not receive keyboard events.
762
+ self._canvas.setAttribute("tabindex", "0")
763
+ self._set_event_handlers()
764
+
765
+ # When the canvas gets focus, it gains a white outline around it. Remove it.
766
+ style = js.document.createElement("style")
767
+ style.innerHTML = "#%s:focus { outline: none; }" % canvas_name
768
+ js.document.head.appendChild(style)
769
+
770
+ # Context must be created after window is created.
771
+ self._assign_config()
772
+
773
+ self.context.attach(self)
774
+
775
+ rect = self._canvas.getBoundingClientRect()
776
+ self.adjust_scale(rect.width, rect.height)
777
+
778
+
779
+ __all__ = ['EmscriptenWindow']