pyglet 3.0.dev8__py3-none-any.whl → 3.0.dev10__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 (142) hide show
  1. pyglet/__init__.py +11 -2
  2. pyglet/__init__.pyi +1 -0
  3. pyglet/app/__init__.py +3 -0
  4. pyglet/app/cocoa.py +88 -39
  5. pyglet/app/ios.py +168 -0
  6. pyglet/config/gl/__init__.py +5 -0
  7. pyglet/config/gl/ios.py +45 -0
  8. pyglet/config/gl/macos.py +1 -1
  9. pyglet/config/gl/windows.py +17 -17
  10. pyglet/display/__init__.py +3 -0
  11. pyglet/display/headless.py +1 -1
  12. pyglet/display/ios.py +49 -0
  13. pyglet/enums.py +38 -0
  14. pyglet/experimental/geoshader_sprite.py +157 -125
  15. pyglet/experimental/particles.py +225 -39
  16. pyglet/font/__init__.py +2 -2
  17. pyglet/font/quartz.py +45 -45
  18. pyglet/graphics/__init__.py +10 -1
  19. pyglet/graphics/api/__init__.py +12 -6
  20. pyglet/graphics/api/base.py +38 -66
  21. pyglet/graphics/api/gl/__init__.py +1 -1
  22. pyglet/graphics/api/gl/base.py +42 -11
  23. pyglet/graphics/api/gl/buffer.py +25 -25
  24. pyglet/graphics/api/gl/cocoa/context.py +1 -1
  25. pyglet/graphics/api/gl/context.py +19 -20
  26. pyglet/graphics/api/gl/framebuffer.py +18 -22
  27. pyglet/graphics/api/gl/gl.py +3051 -2622
  28. pyglet/graphics/api/gl/gl_compat.py +7795 -0
  29. pyglet/graphics/api/gl/gl_fallback.py +98 -0
  30. pyglet/graphics/api/gl/gles.py +3693 -0
  31. pyglet/graphics/api/gl/global_opengl.py +20 -16
  32. pyglet/graphics/api/gl/ios/__init__.py +3 -0
  33. pyglet/graphics/api/gl/ios/context.py +131 -0
  34. pyglet/graphics/api/gl/lib.py +2 -18
  35. pyglet/graphics/api/gl/shader.py +31 -29
  36. pyglet/graphics/api/gl/shapes.py +3 -2
  37. pyglet/graphics/api/gl/sprite.py +9 -6
  38. pyglet/graphics/api/gl/state.py +13 -9
  39. pyglet/graphics/api/gl/text.py +22 -17
  40. pyglet/graphics/api/gl/texture.py +72 -47
  41. pyglet/graphics/api/gl/vertexarray.py +8 -7
  42. pyglet/graphics/api/gl/vertexdomain.py +13 -208
  43. pyglet/graphics/api/gl/win32/context.py +7 -7
  44. pyglet/graphics/api/gl/xlib/context.py +15 -15
  45. pyglet/graphics/api/gl2/buffer.py +2 -2
  46. pyglet/graphics/api/gl2/framebuffer.py +1 -3
  47. pyglet/graphics/api/gl2/global_opengl.py +13 -7
  48. pyglet/graphics/api/gl2/shader.py +5 -2
  49. pyglet/graphics/api/gl2/shapes.py +3 -2
  50. pyglet/graphics/api/gl2/sprite.py +6 -4
  51. pyglet/graphics/api/gl2/text.py +22 -13
  52. pyglet/graphics/api/webgl/__init__.py +17 -26
  53. pyglet/graphics/api/webgl/buffer.py +16 -16
  54. pyglet/graphics/api/webgl/context.py +9 -54
  55. pyglet/graphics/api/webgl/framebuffer.py +16 -22
  56. pyglet/graphics/api/webgl/gl.py +1 -1
  57. pyglet/graphics/api/webgl/gl_info.py +1 -1
  58. pyglet/graphics/api/webgl/shader.py +23 -21
  59. pyglet/graphics/api/webgl/shapes.py +3 -2
  60. pyglet/graphics/api/webgl/sprite.py +9 -6
  61. pyglet/graphics/api/webgl/state.py +13 -15
  62. pyglet/graphics/api/webgl/text.py +22 -13
  63. pyglet/graphics/api/webgl/texture.py +70 -51
  64. pyglet/graphics/api/webgl/vertexarray.py +8 -7
  65. pyglet/graphics/api/webgl/vertexdomain.py +7 -4
  66. pyglet/graphics/atlas.py +4 -4
  67. pyglet/graphics/buffer.py +19 -4
  68. pyglet/graphics/draw.py +50 -30
  69. pyglet/graphics/framebuffer.py +3 -3
  70. pyglet/graphics/instance.py +308 -1
  71. pyglet/graphics/resource.py +124 -0
  72. pyglet/graphics/shader.py +142 -48
  73. pyglet/graphics/state.py +5 -1
  74. pyglet/graphics/texture.py +32 -41
  75. pyglet/graphics/vertexdomain.py +63 -8
  76. pyglet/gui/ninepatch.py +25 -15
  77. pyglet/gui/widgets.py +4 -3
  78. pyglet/image/__init__.py +9 -14
  79. pyglet/image/animation.py +4 -9
  80. pyglet/image/base.py +2 -20
  81. pyglet/image/codecs/quartz.py +14 -14
  82. pyglet/input/macos/darwin_gc.py +11 -23
  83. pyglet/lib.py +5 -10
  84. pyglet/libs/__init__.py +25 -0
  85. pyglet/libs/darwin/__init__.py +18 -1
  86. pyglet/libs/darwin/agl.py +3 -1
  87. pyglet/libs/darwin/cocoapy/__init__.py +19 -7
  88. pyglet/libs/darwin/cocoapy/cocoatypes.py +17 -6
  89. pyglet/libs/darwin/cocoapy/lib_appkit.py +148 -0
  90. pyglet/libs/darwin/cocoapy/lib_cocoa.py +76 -0
  91. pyglet/libs/darwin/cocoapy/lib_corefoundation.py +253 -0
  92. pyglet/libs/darwin/cocoapy/lib_coregraphics.py +182 -0
  93. pyglet/libs/darwin/cocoapy/lib_coretext.py +123 -0
  94. pyglet/libs/darwin/cocoapy/lib_quartz.py +74 -0
  95. pyglet/libs/darwin/cocoapy/lib_uikit.py +7 -0
  96. pyglet/libs/darwin/cocoapy/runtime.py +227 -64
  97. pyglet/libs/darwin/coreaudio.py +3 -7
  98. pyglet/libs/darwin/lib_agl.py +13 -3
  99. pyglet/libs/egl/__init__.py +1 -2
  100. pyglet/libs/egl/egl.py +728 -565
  101. pyglet/libs/egl/lib.py +15 -8
  102. pyglet/libs/linux/glx/glx.py +439 -568
  103. pyglet/libs/linux/glx/lib_glx.py +3 -2
  104. pyglet/libs/win32/lib_wgl.py +3 -2
  105. pyglet/libs/win32/types.py +66 -0
  106. pyglet/libs/win32/wgl.py +359 -258
  107. pyglet/media/__init__.py +64 -5
  108. pyglet/media/codecs/base.py +0 -8
  109. pyglet/media/player.py +161 -103
  110. pyglet/model/__init__.py +2 -0
  111. pyglet/model/codecs/obj.py +2 -2
  112. pyglet/resource.py +1 -1
  113. pyglet/shapes.py +254 -78
  114. pyglet/sprite.py +102 -22
  115. pyglet/text/__init__.py +22 -4
  116. pyglet/text/caret.py +3 -2
  117. pyglet/text/formats/structured.py +8 -7
  118. pyglet/text/layout/base.py +47 -6
  119. pyglet/text/layout/boxes.py +40 -10
  120. pyglet/text/layout/incremental.py +3 -2
  121. pyglet/text/layout/scrolling.py +3 -2
  122. pyglet/window/__init__.py +4 -2
  123. pyglet/window/{cocoa → apple/cocoa}/__init__.py +72 -9
  124. pyglet/window/{cocoa → apple/cocoa}/pyglet_delegate.py +17 -21
  125. pyglet/window/{cocoa → apple/cocoa}/pyglet_textview.py +37 -40
  126. pyglet/window/{cocoa → apple/cocoa}/pyglet_view.py +37 -35
  127. pyglet/window/{cocoa → apple/cocoa}/pyglet_window.py +10 -15
  128. pyglet/window/apple/ios.py +212 -0
  129. pyglet/window/camera/base.py +11 -1
  130. {pyglet-3.0.dev8.dist-info → pyglet-3.0.dev10.dist-info}/METADATA +1 -1
  131. {pyglet-3.0.dev8.dist-info → pyglet-3.0.dev10.dist-info}/RECORD +135 -125
  132. pyglet/graphics/api/gl1/gl_compat.py +0 -6182
  133. pyglet/libs/darwin/cocoapy/cocoalibs.py +0 -827
  134. pyglet/libs/egl/eglext.py +0 -43
  135. pyglet/libs/linux/glx/glxext_arb.py +0 -1168
  136. pyglet/libs/linux/glx/glxext_mesa.py +0 -8
  137. pyglet/libs/linux/glx/glxext_nv.py +0 -980
  138. pyglet/libs/win32/wglext_arb.py +0 -1606
  139. /pyglet/{graphics/api/gl1 → window/apple}/__init__.py +0 -0
  140. /pyglet/window/{cocoa → apple/cocoa}/systemcursor.py +0 -0
  141. {pyglet-3.0.dev8.dist-info → pyglet-3.0.dev10.dist-info}/WHEEL +0 -0
  142. {pyglet-3.0.dev8.dist-info → pyglet-3.0.dev10.dist-info}/licenses/LICENSE +0 -0
pyglet/__init__.py CHANGED
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  from typing import Any, Callable, ItemsView, Sized
19
19
 
20
20
  #: The release version
21
- version = '3.0.dev8'
21
+ version = '3.0.dev10'
22
22
  __version__ = version
23
23
 
24
24
  MIN_PYTHON_VERSION = 3, 10
@@ -264,6 +264,15 @@ class Options:
264
264
 
265
265
  .. versionadded:: 2.0.5"""
266
266
 
267
+ osx_displaylink: bool = True
268
+ """If ``True`` (the default), macOS 14 and later uses ``CADisplayLink`` to schedule window draws.
269
+
270
+ Set this to ``False`` before calling :func:`pyglet.app.run` to retain the clock-driven redraw behavior used by
271
+ earlier pyglet versions. This is useful for applications that need a timer defined frame cadence, such as a
272
+ deterministic capture pipeline or an application that coordinates drawing with an external scheduler.
273
+
274
+ .. versionadded:: 3.0.0"""
275
+
267
276
  dpi_scaling: Literal["platform", "stretch"] = "platform"
268
277
  """For 'HiDPI' displays, Window behavior can differ between operating systems. Defaults to `'platform'`.
269
278
 
@@ -354,7 +363,7 @@ for _option_name, _type_str in options.__annotations__.items():
354
363
 
355
364
 
356
365
  if (__debug__ is False) or getattr(sys, "frozen", False):
357
- options.debug_gl = False
366
+ options.debug_api = False
358
367
 
359
368
 
360
369
  # Call tracing
pyglet/__init__.pyi CHANGED
@@ -68,6 +68,7 @@ class Options:
68
68
  win32_disable_xinput: bool
69
69
  com_mta: bool
70
70
  osx_alt_loop: bool
71
+ osx_displaylink: bool
71
72
  dpi_scaling: Literal["platform", "stretch"]
72
73
  shader_bind_management: bool
73
74
  wayland: bool
pyglet/app/__init__.py CHANGED
@@ -51,6 +51,9 @@ else:
51
51
  # Use alternate loop only if forced, or using an M1 chip.
52
52
  if (platform.machine() == 'arm64' and "M1" in get_chip_model()) or pyglet.options.osx_alt_loop:
53
53
  from pyglet.app.cocoa import CocoaAlternateEventLoop as EventLoop
54
+ elif compat_platform == 'ios':
55
+ from pyglet.app.ios import IOSEventLoop as EventLoop
56
+ from pyglet.app.ios import IOSPlatformEventLoop as PlatformEventLoop
54
57
  elif compat_platform in ('win32', 'cygwin'):
55
58
  from pyglet.app.win32 import Win32EventLoop as PlatformEventLoop
56
59
  elif compat_platform == 'linux':
pyglet/app/cocoa.py CHANGED
@@ -2,13 +2,16 @@ from __future__ import annotations
2
2
 
3
3
  import signal
4
4
  import time
5
+ from typing import TYPE_CHECKING
5
6
 
7
+ import pyglet
6
8
  from pyglet import app
7
- from pyglet.app.base import EventLoop, PlatformEventLoop
8
- from pyglet.libs.darwin import cocoapy, AutoReleasePool, ObjCSubclass, PyObjectEncoding, ObjCInstance, send_super, \
9
- ObjCClass, get_selector, set_realtime_thread_policy
9
+ from pyglet.app.base import EventLoop, PlatformEventLoop, WindowDrawSource
10
+ from pyglet.libs.darwin import cocoapy, AutoReleasePool, PyObjectEncoding, ObjCInstance, send_super, \
11
+ ObjCClass, get_selector, objc_method, set_realtime_thread_policy
10
12
 
11
13
  NSApplication = cocoapy.ObjCClass('NSApplication')
14
+ NSObject = cocoapy.ObjCClass('NSObject')
12
15
  NSMenu = cocoapy.ObjCClass('NSMenu')
13
16
  NSMenuItem = cocoapy.ObjCClass('NSMenuItem')
14
17
  NSDate = cocoapy.ObjCClass('NSDate')
@@ -17,6 +20,9 @@ NSUserDefaults = cocoapy.ObjCClass('NSUserDefaults')
17
20
  NSTimer = cocoapy.ObjCClass('NSTimer')
18
21
  NSRunningApplication = cocoapy.ObjCClass('NSRunningApplication')
19
22
 
23
+ if TYPE_CHECKING:
24
+ from pyglet.window.apple.cocoa import CocoaWindow
25
+
20
26
  def add_menu_item(menu, title, action, key):
21
27
  with AutoReleasePool():
22
28
  title = cocoapy.CFSTR(title)
@@ -54,25 +60,24 @@ def create_menu():
54
60
  appMenuItem.release()
55
61
 
56
62
 
57
- class _AppDelegate_Implementation:
58
- _AppDelegate = ObjCSubclass('NSObject', '_AppDelegate')
63
+ class _AppDelegate(NSObject):
59
64
 
60
- @_AppDelegate.method(b'@' + PyObjectEncoding)
65
+ @objc_method(b'@' + PyObjectEncoding)
61
66
  def init(self, pyglet_loop):
62
67
  objc = ObjCInstance(send_super(self, 'init'))
63
68
  self._pyglet_loop = pyglet_loop
64
69
  return objc # objc is self
65
70
 
66
- @_AppDelegate.method('v')
71
+ @objc_method('v')
67
72
  def updatePyglet_(self):
68
73
  self._pyglet_loop.nsapp_step()
69
74
 
70
- @_AppDelegate.method('v@')
75
+ @objc_method('v@')
71
76
  def applicationWillTerminate_(self, notification):
72
77
  self._pyglet_loop.is_running = False
73
78
  self._pyglet_loop.has_exit = True
74
79
 
75
- @_AppDelegate.method('v@')
80
+ @objc_method('v@')
76
81
  def applicationDidFinishLaunching_(self, notification):
77
82
  self._pyglet_loop._finished_launching = True
78
83
 
@@ -96,38 +101,32 @@ class _AppDelegate_Implementation:
96
101
 
97
102
  NSApp.activateIgnoringOtherApps_(True)
98
103
 
99
- @_AppDelegate.method('v@')
104
+ @objc_method('v@')
100
105
  def applicationWillFinishLaunching_(self, notification):
101
106
  pass
102
107
 
103
- @_AppDelegate.method('B')
108
+ @objc_method('B')
104
109
  def applicationSupportsSecureRestorableState_(self):
105
110
  return True
106
111
 
107
- _AppDelegate = ObjCClass('_AppDelegate') # the actual class
108
-
109
112
  class CocoaAlternateEventLoop(EventLoop):
110
113
  """This is an alternate loop developed mainly for ARM64 variants of macOS.
111
- nextEventMatchingMask_untilDate_inMode_dequeue_ is very broken with ctypes calls. Events eventually stop
112
- working properly after X returns. This event loop differs in that it uses the built-in NSApplication event
114
+
115
+ nextEventMatchingMask_untilDate_inMode_dequeue_ is very broken with ctypes calls on M1.
116
+
117
+ Events eventually stop working properly after X returns. This event loop
118
+ differs in that it uses the built-in NSApplication event
113
119
  loop. We tie our schedule into it via timer.
114
120
  """
115
121
  def __init__(self):
116
122
  super().__init__()
117
123
  self.platform_event_loop = None
118
124
 
119
- def run(self, interval: float | None = 1/60):
120
- if interval is None:
121
- pass # do not schedule redraws
122
- elif not interval:
123
- self.clock.schedule(self._redraw_windows)
124
- else:
125
- self.clock.schedule_interval(self._redraw_windows, interval)
126
-
125
+ def run(self, interval: float | None = 1/60) -> None:
127
126
  self.has_exit = False
128
127
 
129
- from pyglet.window import Window
130
- Window._enable_event_queue = False
128
+ from pyglet.window import Window # noqa: PLC0415
129
+ Window._enable_event_queue = False # noqa: SLF001
131
130
 
132
131
  # Dispatch pending events
133
132
  for window in app.windows:
@@ -135,13 +134,18 @@ class CocoaAlternateEventLoop(EventLoop):
135
134
  window.dispatch_pending_events()
136
135
 
137
136
  self.platform_event_loop = app.platform_event_loop
137
+ self._schedule_window_draw(interval)
138
138
 
139
139
  self.dispatch_event('on_enter')
140
140
  self.is_running = True
141
141
 
142
- self.platform_event_loop.nsapp_start(interval or 0)
142
+ try:
143
+ self.platform_event_loop.nsapp_start(interval or 0)
144
+ finally:
145
+ self._unschedule_window_draw()
146
+ self._raise_window_draw_exception()
143
147
 
144
- def exit(self):
148
+ def exit(self) -> None:
145
149
  """Safely exit the event loop at the end of the current iteration.
146
150
 
147
151
  This method is a thread-safe equivalent for setting
@@ -159,12 +163,52 @@ class CocoaAlternateEventLoop(EventLoop):
159
163
  self.platform_event_loop.nsapp_stop()
160
164
 
161
165
 
166
+ class CocoaWindowDrawSource(WindowDrawSource):
167
+ """Display driven drawing backed by one CADisplayLink."""
168
+
169
+ def __init__(self) -> None:
170
+ self._active = False
171
+ self._interval = None
172
+
173
+ def start(self, interval: float) -> bool:
174
+ started_windows = []
175
+ for window in app.windows:
176
+ if not window._start_display_link(interval): # noqa: SLF001
177
+ for started_window in started_windows:
178
+ started_window._stop_display_link() # noqa: SLF001
179
+ return False
180
+ started_windows.append(window)
181
+
182
+ self._interval = interval
183
+ self._active = True
184
+ return True
185
+
186
+ def stop(self) -> None:
187
+ if not self._active:
188
+ return
189
+
190
+ self._active = False
191
+ self._interval = None
192
+ for window in app.windows:
193
+ window._stop_display_link() # noqa: SLF001
194
+
195
+ def add_window(self, window: CocoaWindow) -> bool:
196
+ return not self._active or window._start_display_link(self._interval) # noqa: SLF001
197
+
198
+ def remove_window(self, window: CocoaWindow) -> None:
199
+ window._stop_display_link() # noqa: SLF001
200
+
201
+
162
202
  class CocoaPlatformEventLoop(PlatformEventLoop):
163
203
 
164
204
  def __init__(self):
165
205
  super().__init__()
166
206
  set_realtime_thread_policy()
167
207
  self._timer = None
208
+ nsview = ObjCClass('NSView')
209
+ self._display_link_supported = bool(
210
+ nsview.instancesRespondToSelector_(get_selector('displayLinkWithTarget:selector:'))
211
+ )
168
212
 
169
213
  with AutoReleasePool():
170
214
  # Prepare the default application.
@@ -198,7 +242,7 @@ class CocoaPlatformEventLoop(PlatformEventLoop):
198
242
  signal.signal(signal.SIGINT, term_received)
199
243
  signal.signal(signal.SIGTERM, term_received)
200
244
 
201
- def start(self):
245
+ def start(self) -> None:
202
246
  with AutoReleasePool():
203
247
  if not self.NSApp.isRunning() and not self._finished_launching:
204
248
  # finishLaunching should be called only once. However isRunning will not
@@ -207,9 +251,14 @@ class CocoaPlatformEventLoop(PlatformEventLoop):
207
251
  self.NSApp.activateIgnoringOtherApps_(True)
208
252
  self._finished_launching = True
209
253
 
210
- def nsapp_start(self, interval):
211
- """Used only for CocoaAlternateEventLoop"""
212
- from pyglet.app import event_loop
254
+ def create_window_draw_source(self, _event_loop: EventLoop) -> WindowDrawSource | None:
255
+ if pyglet.options.osx_displaylink and self._display_link_supported:
256
+ return CocoaWindowDrawSource()
257
+ return None
258
+
259
+ def nsapp_start(self, interval: float) -> None:
260
+ """Used only for CocoaAlternateEventLoop."""
261
+ from pyglet.app import event_loop # noqa: PLC0415
213
262
  self._event_loop = event_loop
214
263
 
215
264
  assert self._timer is None
@@ -219,26 +268,26 @@ class CocoaPlatformEventLoop(PlatformEventLoop):
219
268
  self.appdelegate,
220
269
  get_selector('updatePyglet:'),
221
270
  False,
222
- True
271
+ True,
223
272
  )
224
273
 
225
274
  self.NSApp.run()
226
275
 
227
- def nsapp_step(self):
228
- """Used only for CocoaAlternateEventLoop"""
276
+ def nsapp_step(self) -> None:
277
+ """Used only for CocoaAlternateEventLoop."""
229
278
  self._event_loop.idle()
230
279
  with AutoReleasePool():
231
280
  self.dispatch_posted_events()
232
281
 
233
- def nsapp_stop(self):
234
- """Used only for CocoaAlternateEventLoop"""
282
+ def nsapp_stop(self) -> None:
283
+ """Used only for CocoaAlternateEventLoop."""
235
284
  self.NSApp.stop_(None)
236
285
 
237
286
  if self._timer:
238
287
  self._timer.invalidate()
239
288
  self._timer = None
240
289
 
241
- def step(self, timeout=None):
290
+ def step(self, timeout: float | None=None) -> bool: # noqa: ANN201
242
291
  with AutoReleasePool():
243
292
  self.dispatch_posted_events()
244
293
 
@@ -274,10 +323,10 @@ class CocoaPlatformEventLoop(PlatformEventLoop):
274
323
 
275
324
  return did_time_out
276
325
 
277
- def stop(self):
326
+ def stop(self) -> None:
278
327
  pass
279
328
 
280
- def notify(self):
329
+ def notify(self) -> None:
281
330
  with AutoReleasePool():
282
331
  notifyEvent = NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
283
332
  cocoapy.NSApplicationDefined, # type
pyglet/app/ios.py ADDED
@@ -0,0 +1,168 @@
1
+ """UIKit app loop integration on iOS."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+
6
+ from pyglet import app
7
+ from pyglet.app.base import EventLoop, PlatformEventLoop, WindowDrawSource
8
+ from pyglet.libs.darwin import get_display_link_dt
9
+ from pyglet.libs.darwin.cocoapy import (
10
+ CAFrameRateRange,
11
+ NSRunLoopCommonModes,
12
+ ObjCClass,
13
+ ObjCInstance,
14
+ PyObjectEncoding,
15
+ get_selector,
16
+ objc_method,
17
+ send_super,
18
+ )
19
+
20
+ _XCTEST_ENVIRONMENT = (
21
+ "XCTestBundlePath",
22
+ "XCTestConfigurationFilePath",
23
+ "XCTestSessionIdentifier",
24
+ )
25
+
26
+ def _is_xctest() -> bool:
27
+ return any(name in os.environ for name in _XCTEST_ENVIRONMENT)
28
+
29
+ NSObject = ObjCClass('NSObject')
30
+ NSRunLoop = ObjCClass('NSRunLoop')
31
+ CADisplayLink = ObjCClass('CADisplayLink')
32
+
33
+
34
+ class _PygletIOSDisplayLinkTarget(NSObject):
35
+
36
+ @objc_method(b'@' + PyObjectEncoding)
37
+ def initWithDrawSource_(self, draw_source: IOSDisplayLinkDrawSource) -> ObjCInstance | None:
38
+ self = ObjCInstance(send_super(self, 'init'))
39
+ if not self:
40
+ return None
41
+ self._draw_source = draw_source
42
+ return self
43
+
44
+ @objc_method('v@')
45
+ def displayLinkFired_(self, display_link: ObjCInstance) -> None:
46
+ self._draw_source.tick(display_link)
47
+
48
+
49
+ class IOSDisplayLinkDrawSource(WindowDrawSource):
50
+ """One UIKit display link that paces all pyglet window drawing."""
51
+
52
+ def __init__(self, event_loop: IOSEventLoop) -> None:
53
+ self._event_loop = event_loop
54
+ self._display_link = None
55
+ self._target = None
56
+ self._last_timestamp: float | None = None
57
+
58
+ def start(self, interval: float) -> bool:
59
+ if self._display_link is not None:
60
+ return True
61
+
62
+ self._target = _PygletIOSDisplayLinkTarget.alloc().initWithDrawSource_(self)
63
+ display_link = CADisplayLink.displayLinkWithTarget_selector_(
64
+ self._target,
65
+ get_selector('displayLinkFired:'),
66
+ )
67
+ if display_link is None:
68
+ self._target.release()
69
+ self._target = None
70
+ return False
71
+
72
+ if interval:
73
+ frame_rate = 1 / interval
74
+ if CADisplayLink.instancesRespondToSelector_(get_selector('setPreferredFrameRateRange:')):
75
+ display_link.setPreferredFrameRateRange_(
76
+ CAFrameRateRange(frame_rate, frame_rate, frame_rate),
77
+ )
78
+ else:
79
+ display_link.setPreferredFramesPerSecond_(round(frame_rate))
80
+
81
+ self._display_link = display_link
82
+ self._last_timestamp = None
83
+ display_link.addToRunLoop_forMode_(NSRunLoop.mainRunLoop(), NSRunLoopCommonModes)
84
+ return True
85
+
86
+ def stop(self) -> None:
87
+ if self._display_link is not None:
88
+ self._display_link.invalidate()
89
+ self._display_link = None
90
+ if self._target is not None:
91
+ self._target.release()
92
+ self._target = None
93
+ self._last_timestamp = None
94
+
95
+ def tick(self, display_link: ObjCInstance) -> None:
96
+ if self._display_link is None or not self._event_loop.is_running:
97
+ return
98
+ self._last_timestamp, dt = get_display_link_dt(display_link, self._last_timestamp)
99
+ self._event_loop._tick_app(dt) # noqa: SLF001
100
+
101
+ class IOSPlatformEventLoop(PlatformEventLoop):
102
+ """Schedules pyglet work from UIKit's already-running main run loop."""
103
+
104
+ def __init__(self) -> None:
105
+ super().__init__()
106
+
107
+ def notify(self) -> None:
108
+ return None
109
+
110
+ def start(self) -> None:
111
+ self._is_running.set()
112
+
113
+ def step(self, timeout: float | None = None) -> None:
114
+ self.dispatch_posted_events()
115
+
116
+ def stop(self) -> None:
117
+ self._is_running.clear()
118
+
119
+ def create_window_draw_source(self, event_loop: EventLoop) -> WindowDrawSource:
120
+ return IOSDisplayLinkDrawSource(event_loop)
121
+
122
+
123
+ class IOSEventLoop(EventLoop):
124
+ """Event loop similar to what we did for alt macos loop.."""
125
+
126
+ def run(self, interval: float | None = 1 / 60) -> None:
127
+ # Check for XCode environment as it owns the app lifetime and finalizes its embedded
128
+ # interpreter after the test returns. It must not receive native
129
+ # callbacks from a pyglet timer after that point.
130
+ if _is_xctest():
131
+ # Tick app once during test to ensure at least one draw.
132
+ self._tick_app()
133
+ self.has_exit = True
134
+ return
135
+
136
+ self.has_exit = False
137
+ from pyglet.window import Window # noqa: PLC0415
138
+ Window._enable_event_queue = False # noqa: SLF001
139
+ for window in app.windows:
140
+ window.dispatch_pending_events()
141
+
142
+ self._interval = interval
143
+ self.dispatch_event('on_enter')
144
+ self.is_running = True
145
+ app.platform_event_loop.start()
146
+ self._schedule_display_link(interval)
147
+
148
+ def _schedule_display_link(self, interval: float | None) -> None:
149
+ self._window_draw_source = app.platform_event_loop.create_window_draw_source(self)
150
+ if not self._window_draw_source.start(1 / 60 if interval is None else interval):
151
+ raise RuntimeError("Unable to create the iOS CADisplayLink.")
152
+
153
+ def _tick_app(self, draw_dt: float | None = None) -> None:
154
+ app.platform_event_loop.dispatch_posted_events()
155
+ clock_dt = self.clock.update_time()
156
+ self.clock.call_scheduled_functions(clock_dt)
157
+ if self._interval is not None:
158
+ for window in app.windows:
159
+ self._dispatch_platform_draw(window, clock_dt if draw_dt is None else draw_dt)
160
+
161
+ def exit(self) -> None:
162
+ if not self.is_running:
163
+ return
164
+ self.has_exit = True
165
+ self.is_running = False
166
+ self._unschedule_window_draw()
167
+ app.platform_event_loop.stop()
168
+ self.dispatch_event('on_exit')
@@ -120,6 +120,11 @@ def get_surface_config(user_config: UserConfig, surface: pyglet.window.Window) -
120
120
 
121
121
  return match(user_config, surface)
122
122
 
123
+ if pyglet.compat_platform == "ios":
124
+ from pyglet.config.gl.ios import match # noqa: PLC0415
125
+
126
+ return match(user_config, surface)
127
+
123
128
  if pyglet.compat_platform == "emscripten":
124
129
  from pyglet.config.gl.webgl import match # noqa: PLC0415
125
130
 
@@ -0,0 +1,45 @@
1
+ """OpenGL ES surface configuration for UIKit windows."""
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING
5
+
6
+ from pyglet.config.gl import GLSurfaceConfig
7
+ from pyglet.enums import GraphicsAPI
8
+
9
+ if TYPE_CHECKING:
10
+ from pyglet.config import OpenGLUserConfig
11
+ from pyglet.graphics.api.gl.global_opengl import OpenGLBackend
12
+ from pyglet.graphics.api.gl.ios.context import IOSContext
13
+ from pyglet.window.apple.ios import IOSWindow
14
+
15
+
16
+ def match(config: OpenGLUserConfig, window: IOSWindow) -> IOSGLSurfaceConfig | None:
17
+ # iOS only supports GLES2 or 3.
18
+ if config.api not in (GraphicsAPI.OPENGL_ES_2, GraphicsAPI.OPENGL_ES_3):
19
+ return None
20
+ if config.api == GraphicsAPI.OPENGL_ES_3 and (config.major_version or 3) > 3:
21
+ return None
22
+ return IOSGLSurfaceConfig(window, config)
23
+
24
+
25
+ class IOSGLSurfaceConfig(GLSurfaceConfig):
26
+
27
+ def __init__(self, window: IOSWindow, config: OpenGLUserConfig) -> None:
28
+ super().__init__(window, config, handle=None)
29
+ self.double_buffer = True
30
+ self.red_size = self.green_size = self.blue_size = self.alpha_size = 8
31
+ self.buffer_size = 32
32
+ self.depth_size = config.depth_size or 16
33
+ self.stencil_size = config.stencil_size or 0
34
+ self.sample_buffers = self.samples = 0
35
+ self.stereo = self.aux_buffers = 0
36
+ self.major_version = config.major_version or (3 if config.api == GraphicsAPI.OPENGL_ES_3 else 2)
37
+ self.minor_version = config.minor_version or 0
38
+
39
+ def create_context(self, opengl_backend: OpenGLBackend, share: IOSContext | None) -> IOSContext:
40
+ from pyglet.graphics.api.gl.ios.context import IOSContext # noqa: PLC0415
41
+
42
+ return IOSContext(opengl_backend, self._window, self, share)
43
+
44
+ def apply_format(self) -> None:
45
+ return None
pyglet/config/gl/macos.py CHANGED
@@ -12,7 +12,7 @@ from typing import TYPE_CHECKING
12
12
  if TYPE_CHECKING:
13
13
  from pyglet.config import OpenGLUserConfig
14
14
  from pyglet.graphics.api.gl.cocoa.context import NSOpenGLPixelFormat, CocoaContext
15
- from pyglet.window.cocoa import CocoaWindow
15
+ from pyglet.window.apple.cocoa import CocoaWindow
16
16
  from pyglet.graphics.api import OpenGLBackend
17
17
 
18
18
  # Version info, needed as OpenGL different Lion and onward
@@ -6,7 +6,7 @@ from dataclasses import asdict
6
6
 
7
7
  from typing import TYPE_CHECKING
8
8
  from pyglet.config.gl import GLSurfaceConfig
9
- from pyglet.libs.win32 import PIXELFORMATDESCRIPTOR, _gdi32, wglext_arb, wgl
9
+ from pyglet.libs.win32 import PIXELFORMATDESCRIPTOR, _gdi32, wgl
10
10
  from pyglet.libs.win32.constants import PFD_DRAW_TO_WINDOW, PFD_SUPPORT_OPENGL, PFD_DOUBLEBUFFER, \
11
11
  PFD_DOUBLEBUFFER_DONTCARE, PFD_STEREO, PFD_STEREO_DONTCARE, PFD_DEPTH_DONTCARE, PFD_TYPE_RGBA
12
12
  from pyglet.libs.win32.wgl import WGLFunctions
@@ -221,22 +221,22 @@ class GLLegacyConfig(GLSurfaceConfig):
221
221
 
222
222
  class GLSurfaceConfig(GLSurfaceConfig):
223
223
  attribute_ids = { # noqa: RUF012
224
- 'double_buffer': wglext_arb.WGL_DOUBLE_BUFFER_ARB,
225
- 'stereo': wglext_arb.WGL_STEREO_ARB,
226
- 'buffer_size': wglext_arb.WGL_COLOR_BITS_ARB,
227
- 'aux_buffers': wglext_arb.WGL_AUX_BUFFERS_ARB,
228
- 'sample_buffers': wglext_arb.WGL_SAMPLE_BUFFERS_ARB,
229
- 'samples': wglext_arb.WGL_SAMPLES_ARB,
230
- 'red_size': wglext_arb.WGL_RED_BITS_ARB,
231
- 'green_size': wglext_arb.WGL_GREEN_BITS_ARB,
232
- 'blue_size': wglext_arb.WGL_BLUE_BITS_ARB,
233
- 'alpha_size': wglext_arb.WGL_ALPHA_BITS_ARB,
234
- 'depth_size': wglext_arb.WGL_DEPTH_BITS_ARB,
235
- 'stencil_size': wglext_arb.WGL_STENCIL_BITS_ARB,
236
- 'accum_red_size': wglext_arb.WGL_ACCUM_RED_BITS_ARB,
237
- 'accum_green_size': wglext_arb.WGL_ACCUM_GREEN_BITS_ARB,
238
- 'accum_blue_size': wglext_arb.WGL_ACCUM_BLUE_BITS_ARB,
239
- 'accum_alpha_size': wglext_arb.WGL_ACCUM_ALPHA_BITS_ARB,
224
+ 'double_buffer': wgl.WGL_DOUBLE_BUFFER_ARB,
225
+ 'stereo': wgl.WGL_STEREO_ARB,
226
+ 'buffer_size': wgl.WGL_COLOR_BITS_ARB,
227
+ 'aux_buffers': wgl.WGL_AUX_BUFFERS_ARB,
228
+ 'sample_buffers': wgl.WGL_SAMPLE_BUFFERS_ARB,
229
+ 'samples': wgl.WGL_SAMPLES_ARB,
230
+ 'red_size': wgl.WGL_RED_BITS_ARB,
231
+ 'green_size': wgl.WGL_GREEN_BITS_ARB,
232
+ 'blue_size': wgl.WGL_BLUE_BITS_ARB,
233
+ 'alpha_size': wgl.WGL_ALPHA_BITS_ARB,
234
+ 'depth_size': wgl.WGL_DEPTH_BITS_ARB,
235
+ 'stencil_size': wgl.WGL_STENCIL_BITS_ARB,
236
+ 'accum_red_size': wgl.WGL_ACCUM_RED_BITS_ARB,
237
+ 'accum_green_size': wgl.WGL_ACCUM_GREEN_BITS_ARB,
238
+ 'accum_blue_size': wgl.WGL_ACCUM_BLUE_BITS_ARB,
239
+ 'accum_alpha_size': wgl.WGL_ACCUM_ALPHA_BITS_ARB,
240
240
  }
241
241
 
242
242
  def __init__(self, window: Win32Window, pf: int, user_config: OpenGLUserConfig) -> None:
@@ -38,6 +38,9 @@ else:
38
38
  elif compat_platform == 'darwin':
39
39
  from pyglet.display.cocoa import CocoaDisplay as Display
40
40
  from pyglet.display.cocoa import CocoaScreen as Screen
41
+ elif compat_platform == 'ios':
42
+ from pyglet.display.ios import IOSDisplay as Display
43
+ from pyglet.display.ios import IOSScreen as Screen
41
44
  elif compat_platform in ('win32', 'cygwin'):
42
45
  from pyglet.display.win32 import Win32Display as Display
43
46
  from pyglet.display.win32 import Win32Screen as Screen
@@ -41,7 +41,7 @@ class EGLHeadlessDisplay(HeadlessDisplay):
41
41
  num_devices = egl.EGLint()
42
42
  try:
43
43
  egl.eglQueryDevicesEXT(0, None, byref(num_devices))
44
- except pyglet.libs.egl.eglext.MissingFunctionException:
44
+ except pyglet.libs.MissingFunctionException:
45
45
  warnings.warn('No device available for EGL device platform. Using native display type.')
46
46
  display = egl.EGLNativeDisplayType()
47
47
  self._display_connection = egl.eglGetDisplay(display)
pyglet/display/ios.py ADDED
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from pyglet.libs.darwin.cocoapy import ObjCClass
4
+
5
+ from .base import Display, Screen, ScreenMode
6
+
7
+
8
+ UIScreen = ObjCClass('UIScreen')
9
+
10
+
11
+ class IOSDisplay(Display):
12
+ """The single display from UIKit."""
13
+
14
+ def get_screens(self) -> list[IOSScreen]:
15
+ return [IOSScreen(self, UIScreen.mainScreen())]
16
+
17
+
18
+ class IOSScreen(Screen):
19
+ def __init__(self, display: IOSDisplay, ui_screen) -> None:
20
+ self._ui_screen = ui_screen
21
+ bounds = ui_screen.bounds()
22
+ scale = ui_screen.nativeScale()
23
+ super().__init__(display, 0, 0, round(bounds.size.width * scale), round(bounds.size.height * scale))
24
+
25
+ def get_modes(self) -> list[ScreenMode]:
26
+ return [self.get_mode()]
27
+
28
+ def get_mode(self) -> ScreenMode:
29
+ mode = ScreenMode(self)
30
+ mode.width, mode.height, mode.depth, mode.rate = self.width, self.height, None, None
31
+ return mode
32
+
33
+ def set_mode(self, mode: ScreenMode) -> None:
34
+ return None
35
+
36
+ def restore_mode(self) -> None:
37
+ return None
38
+
39
+ def get_dpi(self) -> int:
40
+ return round(96 * self.get_scale())
41
+
42
+ def get_scale(self) -> float:
43
+ return float(self._ui_screen.nativeScale())
44
+
45
+ def get_display_id(self) -> str:
46
+ return 'main'
47
+
48
+ def get_monitor_name(self) -> str:
49
+ return 'iOS Display'