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.
- pyglet/__init__.py +27 -42
- pyglet/app/base.py +2 -2
- pyglet/clock.py +1 -1
- pyglet/display/base.py +31 -21
- pyglet/display/cocoa.py +25 -1
- pyglet/display/headless.py +1 -1
- pyglet/display/win32.py +134 -18
- pyglet/display/xlib.py +285 -70
- pyglet/event.py +17 -1
- pyglet/experimental/README.md +1 -1
- pyglet/experimental/jobs.py +1 -1
- pyglet/experimental/multitexture_sprite.py +2 -2
- pyglet/font/__init__.py +1 -1
- pyglet/font/base.py +8 -5
- pyglet/font/dwrite/__init__.py +13 -8
- pyglet/font/dwrite/dwrite_lib.py +1 -1
- pyglet/font/user.py +1 -1
- pyglet/gl/base.py +8 -4
- pyglet/gl/cocoa.py +4 -0
- pyglet/gl/gl.py +4 -3
- pyglet/gl/gl.pyi +2320 -0
- pyglet/gl/gl_compat.py +7 -18
- pyglet/gl/gl_compat.pyi +3097 -0
- pyglet/gl/xlib.py +24 -0
- pyglet/graphics/shader.py +34 -20
- pyglet/graphics/vertexbuffer.py +1 -1
- pyglet/gui/frame.py +2 -2
- pyglet/gui/widgets.py +1 -1
- pyglet/image/__init__.py +3 -3
- pyglet/image/buffer.py +3 -3
- pyglet/input/base.py +8 -8
- pyglet/input/linux/evdev.py +1 -1
- pyglet/libs/darwin/cocoapy/cocoalibs.py +3 -1
- pyglet/libs/win32/__init__.py +12 -0
- pyglet/libs/win32/constants.py +4 -0
- pyglet/libs/win32/types.py +97 -0
- pyglet/libs/x11/xrandr.py +166 -0
- pyglet/libs/x11/xrender.py +43 -0
- pyglet/libs/x11/xsync.py +43 -0
- pyglet/math.py +40 -49
- pyglet/media/buffered_logger.py +1 -1
- pyglet/media/codecs/ffmpeg.py +18 -34
- pyglet/media/codecs/gstreamer.py +3 -3
- pyglet/media/codecs/pyogg.py +1 -1
- pyglet/media/codecs/wave.py +6 -0
- pyglet/media/codecs/wmf.py +33 -7
- pyglet/media/devices/win32.py +1 -1
- pyglet/media/drivers/base.py +1 -1
- pyglet/media/drivers/directsound/interface.py +4 -0
- pyglet/media/drivers/listener.py +2 -2
- pyglet/media/drivers/xaudio2/interface.py +6 -2
- pyglet/media/drivers/xaudio2/lib_xaudio2.py +1 -1
- pyglet/media/instrumentation.py +2 -2
- pyglet/media/player.py +2 -2
- pyglet/media/player_worker_thread.py +1 -1
- pyglet/media/synthesis.py +1 -1
- pyglet/model/codecs/gltf.py +1 -1
- pyglet/shapes.py +25 -24
- pyglet/sprite.py +1 -1
- pyglet/text/caret.py +44 -5
- pyglet/text/layout/base.py +3 -3
- pyglet/util.py +1 -1
- pyglet/window/__init__.py +54 -14
- pyglet/window/cocoa/__init__.py +27 -0
- pyglet/window/mouse.py +11 -1
- pyglet/window/win32/__init__.py +40 -14
- pyglet/window/xlib/__init__.py +21 -7
- {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/METADATA +1 -1
- {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/RECORD +71 -67
- {pyglet-2.1.5.dist-info → pyglet-2.1.8.dist-info}/LICENSE +0 -0
- {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
|
|
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
|
|
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
|
|
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
|
pyglet/media/instrumentation.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
|
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 &
|
|
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))
|
pyglet/model/codecs/gltf.py
CHANGED
|
@@ -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
|
|
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
|
|
85
|
+
in vec4 colors;
|
|
86
86
|
in float zposition;
|
|
87
|
+
|
|
87
88
|
in float rotation;
|
|
88
89
|
|
|
89
90
|
|
|
90
|
-
out vec4
|
|
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
|
-
|
|
112
|
+
vertex_colors = colors;
|
|
112
113
|
}
|
|
113
114
|
"""
|
|
114
115
|
|
|
115
116
|
fragment_source = """#version 150 core
|
|
116
|
-
in vec4
|
|
117
|
+
in vec4 vertex_colors;
|
|
117
118
|
out vec4 final_color;
|
|
118
119
|
|
|
119
120
|
void main()
|
|
120
121
|
{
|
|
121
|
-
final_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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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__``
|
|
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")
|
pyglet/text/layout/base.py
CHANGED
|
@@ -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
|
-
#
|
|
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
|
|
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
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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
|
"""
|
pyglet/window/cocoa/__init__.py
CHANGED
|
@@ -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:
|