pyglet 2.1.5__py3-none-any.whl → 2.1.8__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 (71) hide show
  1. pyglet/__init__.py +27 -42
  2. pyglet/app/base.py +2 -2
  3. pyglet/clock.py +1 -1
  4. pyglet/display/base.py +31 -21
  5. pyglet/display/cocoa.py +25 -1
  6. pyglet/display/headless.py +1 -1
  7. pyglet/display/win32.py +134 -18
  8. pyglet/display/xlib.py +285 -70
  9. pyglet/event.py +17 -1
  10. pyglet/experimental/README.md +1 -1
  11. pyglet/experimental/jobs.py +1 -1
  12. pyglet/experimental/multitexture_sprite.py +2 -2
  13. pyglet/font/__init__.py +1 -1
  14. pyglet/font/base.py +8 -5
  15. pyglet/font/dwrite/__init__.py +13 -8
  16. pyglet/font/dwrite/dwrite_lib.py +1 -1
  17. pyglet/font/user.py +1 -1
  18. pyglet/gl/base.py +8 -4
  19. pyglet/gl/cocoa.py +4 -0
  20. pyglet/gl/gl.py +4 -3
  21. pyglet/gl/gl.pyi +2320 -0
  22. pyglet/gl/gl_compat.py +7 -18
  23. pyglet/gl/gl_compat.pyi +3097 -0
  24. pyglet/gl/xlib.py +24 -0
  25. pyglet/graphics/shader.py +34 -20
  26. pyglet/graphics/vertexbuffer.py +1 -1
  27. pyglet/gui/frame.py +2 -2
  28. pyglet/gui/widgets.py +1 -1
  29. pyglet/image/__init__.py +3 -3
  30. pyglet/image/buffer.py +3 -3
  31. pyglet/input/base.py +8 -8
  32. pyglet/input/linux/evdev.py +1 -1
  33. pyglet/libs/darwin/cocoapy/cocoalibs.py +3 -1
  34. pyglet/libs/win32/__init__.py +12 -0
  35. pyglet/libs/win32/constants.py +4 -0
  36. pyglet/libs/win32/types.py +97 -0
  37. pyglet/libs/x11/xrandr.py +166 -0
  38. pyglet/libs/x11/xrender.py +43 -0
  39. pyglet/libs/x11/xsync.py +43 -0
  40. pyglet/math.py +40 -49
  41. pyglet/media/buffered_logger.py +1 -1
  42. pyglet/media/codecs/ffmpeg.py +18 -34
  43. pyglet/media/codecs/gstreamer.py +3 -3
  44. pyglet/media/codecs/pyogg.py +1 -1
  45. pyglet/media/codecs/wave.py +6 -0
  46. pyglet/media/codecs/wmf.py +33 -7
  47. pyglet/media/devices/win32.py +1 -1
  48. pyglet/media/drivers/base.py +1 -1
  49. pyglet/media/drivers/directsound/interface.py +4 -0
  50. pyglet/media/drivers/listener.py +2 -2
  51. pyglet/media/drivers/xaudio2/interface.py +6 -2
  52. pyglet/media/drivers/xaudio2/lib_xaudio2.py +1 -1
  53. pyglet/media/instrumentation.py +2 -2
  54. pyglet/media/player.py +2 -2
  55. pyglet/media/player_worker_thread.py +1 -1
  56. pyglet/media/synthesis.py +1 -1
  57. pyglet/model/codecs/gltf.py +1 -1
  58. pyglet/shapes.py +25 -24
  59. pyglet/sprite.py +1 -1
  60. pyglet/text/caret.py +44 -5
  61. pyglet/text/layout/base.py +3 -3
  62. pyglet/util.py +1 -1
  63. pyglet/window/__init__.py +54 -14
  64. pyglet/window/cocoa/__init__.py +27 -0
  65. pyglet/window/mouse.py +11 -1
  66. pyglet/window/win32/__init__.py +40 -14
  67. pyglet/window/xlib/__init__.py +21 -7
  68. {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/METADATA +1 -1
  69. {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/RECORD +71 -67
  70. {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/LICENSE +0 -0
  71. {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/WHEEL +0 -0
@@ -8,6 +8,7 @@ import pyglet
8
8
  from pyglet.libs.win32 import com
9
9
  from pyglet.media.devices import get_audio_device_manager
10
10
  from pyglet.media.devices.base import DeviceFlow
11
+ from pyglet.media.exceptions import MediaException
11
12
  from pyglet.util import debug_print
12
13
 
13
14
  from . import lib_xaudio2 as lib
@@ -27,6 +28,9 @@ def create_xa2_buffer(audio_data):
27
28
 
28
29
 
29
30
  def create_xa2_waveformat(audio_format):
31
+ if audio_format.channels > 2 or audio_format.sample_size not in (8, 16):
32
+ raise MediaException(f'Unsupported audio format: {audio_format}')
33
+
30
34
  wfx = lib.WAVEFORMATEX()
31
35
  wfx.wFormatTag = lib.WAVE_FORMAT_PCM
32
36
  wfx.nChannels = audio_format.channels
@@ -88,7 +92,7 @@ class XA2EngineCallback(com.COMObject):
88
92
  def OnCriticalError(self, hresult):
89
93
  # This is a textbook bad example, yes.
90
94
  # It's probably safe though: assuming that XA2 has ceased to operate if we ever end up
91
- # here, nothing can release the lock inbetween.
95
+ # here, nothing can release the lock in between.
92
96
  if self._lock.locked():
93
97
  self._lock.release()
94
98
  raise Exception("Critical Error:", hresult)
@@ -438,7 +442,7 @@ class XA2SourceVoice:
438
442
  self.channel_count = channel_count
439
443
  self.sample_size = sample_size
440
444
 
441
- # How many samples the voice had played when it was most recently readded into the
445
+ # How many samples the voice had played when it was most recently re-added into the
442
446
  # pool of available voices.
443
447
  self.samples_played_at_last_recycle = 0
444
448
 
@@ -576,7 +576,7 @@ class XAUDIO2FX_REVERB_PARAMETERS(Structure):
576
576
  ('RearDelay', UINT32), # 7.1: [0, 20] in ms, all other: [0, 5] in ms
577
577
  ('SideDelay', UINT32), # .1: [0, 5] in ms, all other: not used, but still validated # WIN 10 only.
578
578
 
579
- # Indexed Paremeters
579
+ # Indexed Parameters
580
580
  ('PositionLeft', BYTE), # [0, 30] no units
581
581
  ('PositionRight', BYTE), # 0, 30] no units, ignored when configured to mono
582
582
  ('PositionMatrixLeft', BYTE), # [0, 30] no units
@@ -1,5 +1,5 @@
1
1
  """
2
- Responsabilities
2
+ Responsibilities
3
3
 
4
4
  Defines the events that modify media_player state
5
5
  Defines which events are potential defects
@@ -15,7 +15,7 @@ mp_events = {
15
15
  # <evname>: {
16
16
  # "desc": <description used in reports to mention the event>,
17
17
  # "update_names": <list of names of fields updated>,
18
- # "other_fields": <list of additionals fields to show when mention the event in a report>
18
+ # "other_fields": <list of additional fields to show when mention the event in a report>
19
19
  # },
20
20
 
21
21
  "crash": {
pyglet/media/player.py CHANGED
@@ -69,7 +69,7 @@ class _PlayerProperty:
69
69
 
70
70
  We want the Player to have attributes like volume, pitch, etc. These are
71
71
  actually implemented by the AudioPlayer. So this descriptor will forward
72
- an assignement to one of the attributes to the AudioPlayer. For example
72
+ an assignment to one of the attributes to the AudioPlayer. For example
73
73
  `player.volume = 0.5` will call `player._audio_player.set_volume(0.5)`.
74
74
 
75
75
  The Player class has default values at the class level which are retrieved
@@ -97,7 +97,7 @@ class _PlayerProperty:
97
97
  class Player(pyglet.event.EventDispatcher):
98
98
  """High-level sound and video player."""
99
99
 
100
- # Spacialisation attributes, preserved between audio players
100
+ # Specialisation attributes, preserved between audio players
101
101
  _volume = 1.0
102
102
  _min_distance = 1.0
103
103
  _max_distance = 100000000.
@@ -24,7 +24,7 @@ class PlayerWorkerThread(threading.Thread):
24
24
  to keep their buffers filled (and perform event dispatching tasks), but
25
25
  does not block the main thread.
26
26
 
27
- This thread will sleep for a small period betwen updates, but provides a
27
+ This thread will sleep for a small period between updates, but provides a
28
28
  :py:meth:`~notify` method to allow waking it immediately. A :py:meth:`~stop`
29
29
  method is provided to terminate the thread, but under normal operation it
30
30
  will exit cleanly on interpreter shutdown.
pyglet/media/synthesis.py CHANGED
@@ -352,7 +352,7 @@ def sine_operator(sample_rate: int = 44800, frequency: float = 440, index: float
352
352
  def composite_operator(*operators: Generator) -> Generator:
353
353
  """Combine the output from multiple generators.
354
354
 
355
- This does a simple sum & devision of the output of
355
+ This does a simple sum & division of the output of
356
356
  two or more generators. A new generator is returned.
357
357
  """
358
358
  return (sum(samples) / len(samples) for samples in zip(*operators))
@@ -127,7 +127,7 @@ class Accessor:
127
127
  # This is a 'sparse' accessor:
128
128
  self.sparse = data.get('sparse')
129
129
  if self.sparse:
130
- raise NotImplementedError("Not yet implmented")
130
+ raise NotImplementedError("Not yet implemented")
131
131
 
132
132
  # The Python format type:
133
133
  self.fmt = _array_types[self.component_type]
pyglet/shapes.py CHANGED
@@ -82,12 +82,13 @@ if TYPE_CHECKING:
82
82
  vertex_source = """#version 150 core
83
83
  in vec2 position;
84
84
  in vec2 translation;
85
- in vec4 color;
85
+ in vec4 colors;
86
86
  in float zposition;
87
+
87
88
  in float rotation;
88
89
 
89
90
 
90
- out vec4 vertex_color;
91
+ out vec4 vertex_colors;
91
92
 
92
93
  uniform WindowBlock
93
94
  {
@@ -108,17 +109,17 @@ vertex_source = """#version 150 core
108
109
  m_rotation[1][1] = cos(-radians(rotation));
109
110
 
110
111
  gl_Position = window.projection * window.view * m_translate * m_rotation * vec4(position, zposition, 1.0);
111
- vertex_color = color;
112
+ vertex_colors = colors;
112
113
  }
113
114
  """
114
115
 
115
116
  fragment_source = """#version 150 core
116
- in vec4 vertex_color;
117
+ in vec4 vertex_colors;
117
118
  out vec4 final_color;
118
119
 
119
120
  void main()
120
121
  {
121
- final_color = vertex_color;
122
+ final_color = vertex_colors;
122
123
  // No GL_ALPHA_TEST in core, use shader to discard.
123
124
  if(final_color.a < 0.01){
124
125
  discard;
@@ -228,7 +229,7 @@ def _get_segment(p0: tuple[float, float] | list[float], p1: tuple[float, float]
228
229
  scale2 = min(scale2, 2.0 * thickness)
229
230
 
230
231
  # Make these tuples instead of Vec2 because accessing
231
- # members of Vec2 is suprisingly slow
232
+ # members of Vec2 is surprisingly slow
232
233
  miter1_scaled_p = (v_miter1.x * scale1, v_miter1.y * scale1)
233
234
  miter2_scaled_p = (v_miter2.x * scale2, v_miter2.y * scale2)
234
235
 
@@ -376,12 +377,12 @@ class ShapeBase(ABC):
376
377
  def _update_color(self) -> None:
377
378
  """Send the new colors for each vertex to the GPU.
378
379
 
379
- This method must set the contents of `self._vertex_list.color`
380
+ This method must set the contents of `self._vertex_list.colors`
380
381
  using a list or tuple that contains the RGBA color components
381
382
  for each vertex in the shape. This is usually done by repeating
382
383
  `self._rgba` for each vertex.
383
384
  """
384
- self._vertex_list.color[:] = self._rgba * self._num_verts
385
+ self._vertex_list.colors[:] = self._rgba * self._num_verts
385
386
 
386
387
  def _update_translation(self) -> None:
387
388
  self._vertex_list.translation[:] = (self._x, self._y) * self._num_verts
@@ -862,7 +863,7 @@ class Arc(ShapeBase):
862
863
  self._vertex_list = self._program.vertex_list(
863
864
  self._num_verts, self._draw_mode, self._batch, self._group,
864
865
  position=('f', self._get_vertices()),
865
- color=('Bn', self._rgba * self._num_verts),
866
+ colors=('Bn', self._rgba * self._num_verts),
866
867
  translation=('f', (self._x, self._y) * self._num_verts))
867
868
 
868
869
  def _get_vertices(self) -> Sequence[float]:
@@ -1032,7 +1033,7 @@ class BezierCurve(ShapeBase):
1032
1033
  self._vertex_list = self._program.vertex_list(
1033
1034
  self._num_verts, self._draw_mode, self._batch, self._group,
1034
1035
  position=('f', self._get_vertices()),
1035
- color=('Bn', self._rgba * self._num_verts),
1036
+ colors=('Bn', self._rgba * self._num_verts),
1036
1037
  translation=('f', (self._x, self._y) * self._num_verts))
1037
1038
 
1038
1039
  def _get_vertices(self) -> Sequence[float]:
@@ -1169,7 +1170,7 @@ class Circle(ShapeBase):
1169
1170
  self._vertex_list = self._program.vertex_list(
1170
1171
  self._segments * 3, self._draw_mode, self._batch, self._group,
1171
1172
  position=('f', self._get_vertices()),
1172
- color=('Bn', self._rgba * self._num_verts),
1173
+ colors=('Bn', self._rgba * self._num_verts),
1173
1174
  translation=('f', (self._x, self._y) * self._num_verts))
1174
1175
 
1175
1176
  def _get_vertices(self) -> Sequence[float]:
@@ -1287,7 +1288,7 @@ class Ellipse(ShapeBase):
1287
1288
  self._vertex_list = self._program.vertex_list(
1288
1289
  self._segments * 3, self._draw_mode, self._batch, self._group,
1289
1290
  position=('f', self._get_vertices()),
1290
- color=('Bn', self._rgba * self._num_verts),
1291
+ colors=('Bn', self._rgba * self._num_verts),
1291
1292
  translation=('f', (self._x, self._y) * self._num_verts))
1292
1293
 
1293
1294
  def _get_vertices(self) -> Sequence[float]:
@@ -1420,7 +1421,7 @@ class Sector(ShapeBase):
1420
1421
  self._vertex_list = self._program.vertex_list(
1421
1422
  self._num_verts, self._draw_mode, self._batch, self._group,
1422
1423
  position=('f', self._get_vertices()),
1423
- color=('Bn', self._rgba * self._num_verts),
1424
+ colors=('Bn', self._rgba * self._num_verts),
1424
1425
  translation=('f', (self._x, self._y) * self._num_verts))
1425
1426
 
1426
1427
  def _get_vertices(self) -> Sequence[float]:
@@ -1562,7 +1563,7 @@ class Line(ShapeBase):
1562
1563
  self._vertex_list = self._program.vertex_list(
1563
1564
  6, self._draw_mode, self._batch, self._group,
1564
1565
  position=('f', self._get_vertices()),
1565
- color=('Bn', self._rgba * self._num_verts),
1566
+ colors=('Bn', self._rgba * self._num_verts),
1566
1567
  translation=('f', (self._x, self._y) * self._num_verts))
1567
1568
 
1568
1569
  def _get_vertices(self) -> Sequence[float]:
@@ -1688,7 +1689,7 @@ class Rectangle(ShapeBase):
1688
1689
  self._vertex_list = self._program.vertex_list(
1689
1690
  6, self._draw_mode, self._batch, self._group,
1690
1691
  position=('f', self._get_vertices()),
1691
- color=('Bn', self._rgba * self._num_verts),
1692
+ colors=('Bn', self._rgba * self._num_verts),
1692
1693
  translation=('f', (self._x, self._y) * self._num_verts))
1693
1694
 
1694
1695
  def _get_vertices(self) -> Sequence[float]:
@@ -1831,11 +1832,11 @@ class BorderedRectangle(ShapeBase):
1831
1832
  self._vertex_list = self._program.vertex_list_indexed(
1832
1833
  8, self._draw_mode, indices, self._batch, self._group,
1833
1834
  position=('f', self._get_vertices()),
1834
- color=('Bn', self._rgba * 4 + self._border_rgba * 4),
1835
+ colors=('Bn', self._rgba * 4 + self._border_rgba * 4),
1835
1836
  translation=('f', (self._x, self._y) * self._num_verts))
1836
1837
 
1837
1838
  def _update_color(self) -> None:
1838
- self._vertex_list.color[:] = self._rgba * 4 + self._border_rgba * 4
1839
+ self._vertex_list.colors[:] = self._rgba * 4 + self._border_rgba * 4
1839
1840
 
1840
1841
  def _get_vertices(self) -> Sequence[float]:
1841
1842
  if not self._visible:
@@ -2045,11 +2046,11 @@ class Box(ShapeBase):
2045
2046
  self._vertex_list = self._program.vertex_list_indexed(
2046
2047
  self._num_verts, self._draw_mode, indices, self._batch, self._group,
2047
2048
  position=('f', self._get_vertices()),
2048
- color=('Bn', self._rgba * self._num_verts),
2049
+ colors=('Bn', self._rgba * self._num_verts),
2049
2050
  translation=('f', (self._x, self._y) * self._num_verts))
2050
2051
 
2051
2052
  def _update_color(self):
2052
- self._vertex_list.color[:] = self._rgba * self._num_verts
2053
+ self._vertex_list.colors[:] = self._rgba * self._num_verts
2053
2054
 
2054
2055
  def _get_vertices(self) -> Sequence[float]:
2055
2056
  if not self._visible:
@@ -2224,7 +2225,7 @@ class RoundedRectangle(pyglet.shapes.ShapeBase):
2224
2225
  self._vertex_list = self._program.vertex_list(
2225
2226
  self._num_verts, self._draw_mode, self._batch, self._group,
2226
2227
  position=('f', self._get_vertices()),
2227
- color=('Bn', self._rgba * self._num_verts),
2228
+ colors=('Bn', self._rgba * self._num_verts),
2228
2229
  translation=('f', (self._x, self._y) * self._num_verts))
2229
2230
 
2230
2231
  def _get_vertices(self) -> Sequence[float]:
@@ -2375,7 +2376,7 @@ class Triangle(ShapeBase):
2375
2376
  self._vertex_list = self._program.vertex_list(
2376
2377
  3, self._draw_mode, self._batch, self._group,
2377
2378
  position=('f', self._get_vertices()),
2378
- color=('Bn', self._rgba * self._num_verts),
2379
+ colors=('Bn', self._rgba * self._num_verts),
2379
2380
  translation=('f', (self._x, self._y) * self._num_verts))
2380
2381
 
2381
2382
  def _get_vertices(self) -> Sequence[float]:
@@ -2511,7 +2512,7 @@ class Star(ShapeBase):
2511
2512
  self._vertex_list = self._program.vertex_list(
2512
2513
  self._num_verts, self._draw_mode, self._batch, self._group,
2513
2514
  position=('f', self._get_vertices()),
2514
- color=('Bn', self._rgba * self._num_verts),
2515
+ colors=('Bn', self._rgba * self._num_verts),
2515
2516
  rotation=('f', (self._rotation,) * self._num_verts),
2516
2517
  translation=('f', (self._x, self._y) * self._num_verts))
2517
2518
 
@@ -2636,7 +2637,7 @@ class Polygon(ShapeBase):
2636
2637
  earcut.earcut(vertices),
2637
2638
  self._batch, self._group,
2638
2639
  position=('f', vertices),
2639
- color=('Bn', self._rgba * self._num_verts),
2640
+ colors=('Bn', self._rgba * self._num_verts),
2640
2641
  translation=('f', (self._x, self._y) * self._num_verts))
2641
2642
 
2642
2643
  def _get_vertices(self) -> Sequence[float]:
@@ -2721,7 +2722,7 @@ class MultiLine(ShapeBase):
2721
2722
  self._vertex_list = self._program.vertex_list(
2722
2723
  self._num_verts, self._draw_mode, self._batch, self._group,
2723
2724
  position=('f', self._get_vertices()),
2724
- color=('Bn', self._rgba * self._num_verts),
2725
+ colors=('Bn', self._rgba * self._num_verts),
2725
2726
  translation=('f', (self._x, self._y) * self._num_verts))
2726
2727
 
2727
2728
  def _get_vertices(self) -> Sequence[float]:
pyglet/sprite.py CHANGED
@@ -180,7 +180,7 @@ def get_default_array_shader() -> ShaderProgram:
180
180
  class SpriteGroup(graphics.Group):
181
181
  """Shared Sprite rendering Group.
182
182
 
183
- The Group defines custom ``__eq__`` ane ``__hash__`` methods, and so will
183
+ The Group defines custom ``__eq__`` and ``__hash__`` methods, and so will
184
184
  be automatically coalesced with other Sprite Groups sharing the same parent
185
185
  Group, Texture and blend parameters.
186
186
  """
pyglet/text/caret.py CHANGED
@@ -18,16 +18,17 @@ from typing import TYPE_CHECKING, Any, Pattern
18
18
 
19
19
  from pyglet import clock, event
20
20
  from pyglet.window import key
21
+ from pyglet.event import EventDispatcher
21
22
 
22
23
  if TYPE_CHECKING:
23
24
  from pyglet.graphics import Batch
25
+ from pyglet.window import Window
24
26
  from pyglet.text.layout import IncrementalTextLayout
25
27
 
26
-
27
- class Caret:
28
+ class Caret(EventDispatcher):
28
29
  """Visible text insertion marker for `pyglet.text.layout.IncrementalTextLayout`.
29
30
 
30
- The caret is drawn as a single vertical bar at the document `position`
31
+ The caret is drawn as a single vertical bar at the document `position`
31
32
  on a text layout object. If ``mark`` is not None, it gives the unmoving
32
33
  end of the current text selection. The visible text selection on the
33
34
  layout is updated along with ``mark`` and ``position``.
@@ -39,6 +40,9 @@ class Caret:
39
40
  Updates to the document (and so the layout) are automatically propagated
40
41
  to the caret.
41
42
 
43
+ If the window argument is supplied, the caret object dispatches the on_clipboard_copy event when copying text and copies the text.
44
+ Pasting also works, which will dispatch the on_clipboard_paste event, and pastes the text to the current position of the caret, overriding selection.
45
+
42
46
  The caret object can be pushed onto a window event handler stack with
43
47
  ``Window.push_handlers``. The caret will respond correctly to keyboard,
44
48
  text, mouse and activation events, including double- and triple-clicks.
@@ -70,7 +74,7 @@ class Caret:
70
74
  _next_attributes: dict[str, Any]
71
75
 
72
76
  def __init__(self, layout: IncrementalTextLayout, batch: Batch | None = None,
73
- color: tuple[int, int, int, int] = (0, 0, 0, 255)) -> None:
77
+ color: tuple[int, int, int, int] = (0, 0, 0, 255), window: Window | None = None) -> None:
74
78
  """Create a caret for a layout.
75
79
 
76
80
  By default the layout's batch is used, so the caret does not need to
@@ -84,7 +88,8 @@ class Caret:
84
88
  `color` : (int, int, int, int)
85
89
  An RGBA or RGB tuple with components in the range [0, 255].
86
90
  RGB colors will be treated as having an opacity of 255.
87
-
91
+ `window` : `~pyglet.window.Window`
92
+ For the clipboard feature to work, a window object is needed to be passed in to access and set clipboard content.
88
93
  """
89
94
  from pyglet import gl
90
95
  self._layout = layout
@@ -110,6 +115,7 @@ class Caret:
110
115
 
111
116
  self.visible = True
112
117
 
118
+ self._window = window
113
119
  layout.push_handlers(self)
114
120
 
115
121
  @property
@@ -467,7 +473,26 @@ class Caret:
467
473
  self.position = 0
468
474
  else:
469
475
  self.position = m.start()
476
+ elif motion == key.MOTION_COPY and self._window:
477
+ pos = self._position
478
+ mark = self._mark
479
+ if pos > mark:
480
+ text = self._layout.document.text[mark:pos]
481
+ else:
482
+ text = self._layout.document.text[pos:mark]
483
+
484
+ self._window.set_clipboard_text(text)
485
+ self.dispatch_event("on_clipboard_copy", text)
486
+ elif motion == key.MOTION_PASTE and self._window:
487
+ if self._mark is not None:
488
+ self._delete_selection()
470
489
 
490
+ text = self._window.get_clipboard_text().replace("\r", "\n")
491
+ pos = self._position
492
+ self._position += len(text)
493
+ self._layout.document.insert_text(pos, text, self._next_attributes)
494
+ self._nudge()
495
+ self.dispatch_event("on_clipboard_paste", text)
471
496
  if self._mark is not None and not select:
472
497
  self._mark = None
473
498
  self._layout.set_selection(0, 0)
@@ -564,3 +589,17 @@ class Caret:
564
589
  self._active = False
565
590
  self.visible = self._active
566
591
  return event.EVENT_HANDLED
592
+
593
+ def on_clipboard_copy(self, text: str) -> bool:
594
+ """Dispatched when text is copied.
595
+ This default handler does nothing.
596
+ """
597
+ return event.EVENT_HANDLED
598
+ def on_clipboard_paste(self, text: str) -> bool:
599
+ """Dispatched when text is pasted.
600
+ The default handler does nothing.
601
+ """
602
+ return event.EVENT_HANDLED
603
+
604
+ Caret.register_event_type("on_clipboard_copy")
605
+ Caret.register_event_type("on_clipboard_paste")
@@ -15,6 +15,7 @@ from typing import (
15
15
 
16
16
  import pyglet
17
17
  from pyglet import graphics
18
+ from pyglet.font.base import GlyphPosition
18
19
  from pyglet.gl import (
19
20
  GL_BLEND,
20
21
  GL_DEPTH_ATTACHMENT,
@@ -33,7 +34,6 @@ from pyglet.gl import (
33
34
  )
34
35
  from pyglet.graphics import Group
35
36
  from pyglet.text import runlist
36
- from pyglet.font.base import GlyphPosition
37
37
 
38
38
  if TYPE_CHECKING:
39
39
  from pyglet.customtypes import AnchorX, AnchorY, ContentVAlign, HorizontalAlign
@@ -448,7 +448,7 @@ class _GlyphBox(_AbstractBox):
448
448
  rotation: float, visible: bool, anchor_x: float, anchor_y: float, context: _LayoutContext) -> None:
449
449
  # Creates the initial attributes and vertex lists of the glyphs.
450
450
  # line_x/line_y are calculated when lines shift. To prevent having to destroy and recalculate the layout
451
- # everytime it moves, they are merged into the vertices. This way the translation can be moved directly.
451
+ # every time it moves, they are merged into the vertices. This way the translation can be moved directly.
452
452
  assert self.glyphs
453
453
  assert not self.vertex_lists
454
454
  try:
@@ -1918,7 +1918,7 @@ class TextLayout:
1918
1918
  owner_glyphs.extend(zip([kern] * (kern_end - kern_start), gs, os))
1919
1919
  if owner is None:
1920
1920
  # Assume glyphs are already boxes.
1921
- for kern, glyph in owner_glyphs:
1921
+ for _, glyph, _ in owner_glyphs:
1922
1922
  line.add_box(glyph)
1923
1923
  else:
1924
1924
  line.add_box(_GlyphBox(owner, font, owner_glyphs, width))
pyglet/util.py CHANGED
@@ -208,7 +208,7 @@ class Decoder:
208
208
  raise NotImplementedError()
209
209
 
210
210
  def decode(self, *args, **kwargs):
211
- """Read and decode the given file object and return an approprite
211
+ """Read and decode the given file object and return an appropriate
212
212
  pyglet object. Throws DecodeException if there is an error.
213
213
  `filename` can be a file type hint.
214
214
  """
pyglet/window/__init__.py CHANGED
@@ -511,9 +511,21 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
511
511
  screen = display.get_default_screen()
512
512
 
513
513
  if not config:
514
- for template_config in [gl.Config(double_buffer=True, depth_size=24, major_version=3, minor_version=3),
515
- gl.Config(double_buffer=True, depth_size=16, major_version=3, minor_version=3),
516
- None]:
514
+ alpha_size = None
515
+ transparent_fb = False
516
+ # Override config settings if intention is transparency.
517
+ if style in ('transparent', 'overlay'):
518
+ # Ensure the framebuffer is large enough to support transparency.
519
+ alpha_size = 8
520
+ transparent_fb = True
521
+
522
+ for template_config in [
523
+ gl.Config(double_buffer=True, depth_size=24, major_version=3, minor_version=3,
524
+ alpha_size=alpha_size, transparent_framebuffer=transparent_fb),
525
+ gl.Config(double_buffer=True, depth_size=16, major_version=3, minor_version=3,
526
+ alpha_size=alpha_size, transparent_framebuffer=transparent_fb),
527
+ None,
528
+ ]:
517
529
  try:
518
530
  config = screen.get_best_config(template_config)
519
531
  break
@@ -522,10 +534,10 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
522
534
  if not config:
523
535
  msg = 'No standard config is available.'
524
536
  raise NoSuchConfigException(msg)
525
-
526
- # Necessary on Windows. More investigation needed:
527
- if style in ('transparent', 'overlay'):
528
- config.alpha = 8
537
+ else:
538
+ if style in ('transparent', 'overlay'):
539
+ config.alpha_size = 8
540
+ config.transparent_framebuffer = True
529
541
 
530
542
  if not config.is_complete():
531
543
  config = screen.get_best_config(config)
@@ -649,7 +661,7 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
649
661
  """Close the window.
650
662
 
651
663
  After closing the window, the GL context will be invalid. The
652
- window instance cannot be reused once closed. To re-use windows,
664
+ window instance cannot be reused once closed. To reuse windows,
653
665
  see :py:meth:`.set_visible` instead.
654
666
 
655
667
  The :py:meth:`pyglet.app.EventLoop.on_window_close` event is
@@ -813,6 +825,21 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
813
825
  """
814
826
  raise NotImplementedError
815
827
 
828
+ def set_mouse_passthrough(self, state: bool) -> None:
829
+ """Set whether the operating system will ignore mouse input from this window.
830
+
831
+ Behavior may differ across operating systems. This is typically used in window overlays with
832
+ transparent frame buffers.
833
+
834
+ Args:
835
+ state:
836
+ ``True`` will allow mouse input to pass through the window to anything behind it. Otherwise, ``False``
837
+ allows the window to accept focus again.
838
+
839
+ .. versionadded:: 2.1.8
840
+ """
841
+ raise NotImplementedError
842
+
816
843
  @abstractmethod
817
844
  def minimize(self) -> None:
818
845
  """Minimize the window."""
@@ -841,16 +868,18 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
841
868
  self.dispatch_event('on_close')
842
869
 
843
870
  def _on_internal_resize(self, width: int, height: int) -> None:
844
- gl.glViewport(0, 0, *self.get_framebuffer_size())
871
+ framebuffer_size = self.get_framebuffer_size()
872
+ gl.glViewport(0, 0, max(framebuffer_size[0], 1), max(framebuffer_size[1], 1))
845
873
  w, h = self.get_size()
846
- self.projection = Mat4.orthogonal_projection(0, w, 0, h, -8192, 8192)
874
+ self.projection = Mat4.orthogonal_projection(0, max(w, 1), 0, max(h, 1), -8192, 8192)
847
875
  self.dispatch_event('on_resize', w, h)
848
876
 
849
877
  def _on_internal_scale(self, scale: float, dpi: int) -> None:
850
- gl.glViewport(0, 0, *self.get_framebuffer_size())
878
+ framebuffer_size = self.get_framebuffer_size()
879
+ gl.glViewport(0, 0, max(framebuffer_size[0], 1), max(framebuffer_size[1], 1))
851
880
  w, h = self.get_size()
852
- self.projection = Mat4.orthogonal_projection(0, w, 0, h, -8192, 8192)
853
- self._mouse_cursor.scaling = scale
881
+ self.projection = Mat4.orthogonal_projection(0, max(w, 1), 0, max(h, 1), -8192, 8192)
882
+ self._mouse_cursor.scaling = self._get_mouse_scale()
854
883
  self.dispatch_event('on_scale', scale, dpi)
855
884
 
856
885
  def on_resize(self, width: int, height: int) -> EVENT_HANDLE_STATE:
@@ -1118,9 +1147,16 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
1118
1147
  if cursor is None:
1119
1148
  cursor = DefaultMouseCursor()
1120
1149
  self._mouse_cursor = cursor
1121
- self._mouse_cursor.scaling = self.scale
1150
+ self._mouse_cursor.scaling = self._get_mouse_scale()
1122
1151
  self.set_mouse_platform_visible()
1123
1152
 
1153
+ def _get_mouse_scale(self):
1154
+ """The mouse scale factoring in the DPI.
1155
+
1156
+ On Mac, this is always 1.0.
1157
+ """
1158
+ return self.scale
1159
+
1124
1160
  def set_exclusive_mouse(self, exclusive: bool = True) -> None:
1125
1161
  """Hide the mouse cursor and direct all mouse events to this window.
1126
1162
 
@@ -1687,6 +1723,8 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
1687
1723
  * MOTION_END_OF_FILE
1688
1724
  * MOTION_BACKSPACE
1689
1725
  * MOTION_DELETE
1726
+ * MOTION_COPY
1727
+ * MOTION_PASTE
1690
1728
 
1691
1729
  :event:
1692
1730
  """
@@ -1717,6 +1755,8 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
1717
1755
  * MOTION_PREVIOUS_PAGE
1718
1756
  * MOTION_BEGINNING_OF_FILE
1719
1757
  * MOTION_END_OF_FILE
1758
+ * MOTION_COPY
1759
+ * MOTION_PASTE
1720
1760
 
1721
1761
  :event:
1722
1762
  """
@@ -70,6 +70,11 @@ class CocoaWindow(BaseWindow):
70
70
  cocoapy.NSClosableWindowMask |
71
71
  cocoapy.NSUtilityWindowMask,
72
72
  BaseWindow.WINDOW_STYLE_BORDERLESS: cocoapy.NSBorderlessWindowMask,
73
+ BaseWindow.WINDOW_STYLE_TRANSPARENT: cocoapy.NSTitledWindowMask |
74
+ cocoapy.NSClosableWindowMask |
75
+ cocoapy.NSMiniaturizableWindowMask,
76
+ BaseWindow.WINDOW_STYLE_OVERLAY: cocoapy.NSBorderlessWindowMask,
77
+
73
78
  }
74
79
 
75
80
  def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003
@@ -161,6 +166,17 @@ class CocoaWindow(BaseWindow):
161
166
  # Then create a view and set it as our NSWindow's content view.
162
167
  self._nsview = PygletView.alloc().initWithFrame_cocoaWindow_(content_rect, self)
163
168
  self._nsview.setWantsBestResolutionOpenGLSurface_(True)
169
+
170
+ if not self._fullscreen:
171
+ if self._style in ("transparent", "overlay"):
172
+ self._nswindow.setOpaque_(False)
173
+ self._nswindow.setBackgroundColor_(NSColor.clearColor())
174
+ self._nswindow.setHasShadow_(False)
175
+
176
+ if self._style == "overlay":
177
+ self.set_mouse_passthrough(True)
178
+ self._nswindow.setLevel_(cocoapy.NSStatusWindowLevel)
179
+
164
180
  self._nswindow.setContentView_(self._nsview)
165
181
  self._nswindow.makeFirstResponder_(self._nsview)
166
182
 
@@ -216,6 +232,17 @@ class CocoaWindow(BaseWindow):
216
232
 
217
233
  return 1.0
218
234
 
235
+ def _get_mouse_scale(self) -> float:
236
+ """The mouse scale factoring in the DPI.
237
+
238
+ On Mac, this is always 1.0.
239
+ """
240
+ return 1.0
241
+
242
+ def set_mouse_passthrough(self, state: bool) -> None:
243
+ with AutoReleasePool():
244
+ self._nswindow.setIgnoresMouseEvents_(state)
245
+
219
246
  def _set_nice_window_location(self) -> None:
220
247
  # Construct a list of all visible windows that aren't us.
221
248
  visible_windows = [win for win in pyglet.app.windows if
pyglet/window/mouse.py CHANGED
@@ -23,7 +23,8 @@ class MouseStateHandler:
23
23
  False
24
24
 
25
25
 
26
- Mouse coordinates can be retrieved by using the ``'x'`` and ``'y'`` strings.
26
+ Mouse coordinates can be retrieved by using the ``'x'`` and ``'y'`` strings
27
+ or by using ``'mouse_state.x'`` and ``'mouse_state.y`` attributes.
27
28
 
28
29
  For example::
29
30
 
@@ -37,6 +38,12 @@ class MouseStateHandler:
37
38
  20
38
39
  >>> mouse_state['y']
39
40
  50
41
+
42
+ # Or...
43
+ >>> mouse_state.x
44
+ 20
45
+ >>> mouse_state.y
46
+ 50
40
47
  """
41
48
 
42
49
  def __init__(self) -> None: # noqa: D107
@@ -64,6 +71,9 @@ class MouseStateHandler:
64
71
 
65
72
  def __getitem__(self, key: str) -> int | bool:
66
73
  return self.data.get(key, False)
74
+
75
+ def __getattr__(self, item: str) -> int:
76
+ return self.data[item]
67
77
 
68
78
 
69
79
  def buttons_string(buttons: int) -> str: