e2D 2.0.0__cp313-cp313-win_amd64.whl → 2.0.2__cp313-cp313-win_amd64.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.
- e2D/__init__.py +199 -72
- e2D/__init__.pyi +145 -0
- e2D/ccolors.c +34514 -0
- e2D/ccolors.cp313-win_amd64.pyd +0 -0
- e2D/ccolors.pyi +51 -0
- e2D/ccolors.pyx +350 -0
- e2D/color_defs.py +238 -0
- e2D/colors.py +380 -0
- e2D/colors.pyi +104 -0
- e2D/commons.py +38 -10
- e2D/commons.pyi +79 -0
- e2D/cvectors.c +152 -152
- e2D/cvectors.cp313-win_amd64.pyd +0 -0
- e2D/cvectors.pyi +243 -0
- e2D/devices.py +19 -6
- e2D/devices.pyi +65 -0
- e2D/plots.py +55 -29
- e2D/plots.pyi +238 -0
- e2D/shapes.py +81 -44
- e2D/shapes.pyi +272 -0
- e2D/test_colors.py +122 -0
- e2D/text_renderer.py +46 -16
- e2D/text_renderer.pyi +118 -0
- e2D/types.py +58 -0
- e2D/types.pyi +61 -0
- e2D/vectors.py +153 -61
- e2D/vectors.pyi +106 -0
- e2D/winrec.py +275 -0
- e2D/winrec.pyi +87 -0
- {e2d-2.0.0.dist-info → e2d-2.0.2.dist-info}/METADATA +95 -26
- e2d-2.0.2.dist-info/RECORD +46 -0
- {e2d-2.0.0.dist-info → e2d-2.0.2.dist-info}/WHEEL +1 -1
- e2d-2.0.0.dist-info/RECORD +0 -26
- {e2d-2.0.0.dist-info → e2d-2.0.2.dist-info}/licenses/LICENSE +0 -0
- {e2d-2.0.0.dist-info → e2d-2.0.2.dist-info}/top_level.txt +0 -0
e2D/shapes.pyi
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type stubs for shapes module
|
|
3
|
+
High-performance 2D shape rendering using SDF and GPU shaders
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import moderngl
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
from typing import Optional, Sequence
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from .types import ContextType, ProgramType, VAOType, VectorType, ColorType, BufferType
|
|
12
|
+
from .color_defs import WHITE, TRANSPARENT
|
|
13
|
+
|
|
14
|
+
class FillMode(Enum):
|
|
15
|
+
"""Shape fill mode"""
|
|
16
|
+
FILL: int
|
|
17
|
+
STROKE: int
|
|
18
|
+
FILL_STROKE: int
|
|
19
|
+
|
|
20
|
+
class ShapeLabel:
|
|
21
|
+
"""A pre-rendered shape for efficient repeated drawing."""
|
|
22
|
+
ctx: ContextType
|
|
23
|
+
prog: ProgramType
|
|
24
|
+
vbo: BufferType
|
|
25
|
+
vertex_count: int
|
|
26
|
+
shape_type: str
|
|
27
|
+
vao: VAOType
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
ctx: ContextType,
|
|
32
|
+
prog: ProgramType,
|
|
33
|
+
vbo: BufferType,
|
|
34
|
+
vertex_count: int,
|
|
35
|
+
shape_type: str = 'line'
|
|
36
|
+
) -> None: ...
|
|
37
|
+
|
|
38
|
+
def draw(self) -> None:
|
|
39
|
+
"""Draw the cached shape."""
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
class InstancedShapeBatch:
|
|
43
|
+
"""High-performance instanced batch for drawing thousands of shapes with minimal CPU overhead."""
|
|
44
|
+
ctx: ContextType
|
|
45
|
+
prog: ProgramType
|
|
46
|
+
shape_type: str
|
|
47
|
+
max_instances: int
|
|
48
|
+
instance_count: int
|
|
49
|
+
floats_per_instance: int
|
|
50
|
+
instance_buffer: BufferType
|
|
51
|
+
quad_vbo: BufferType
|
|
52
|
+
vao: VAOType
|
|
53
|
+
instance_data: list[float]
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
ctx: ContextType,
|
|
58
|
+
prog: ProgramType,
|
|
59
|
+
shape_type: str = 'circle',
|
|
60
|
+
max_instances: int = 100000
|
|
61
|
+
) -> None: ...
|
|
62
|
+
|
|
63
|
+
def add_circle(
|
|
64
|
+
self,
|
|
65
|
+
center: VectorType,
|
|
66
|
+
radius: float,
|
|
67
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
68
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
69
|
+
border_width: float = 0.0,
|
|
70
|
+
antialiasing: float = 1.0
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Add a circle instance to the batch."""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def add_circles_numpy(
|
|
76
|
+
self,
|
|
77
|
+
centers: npt.NDArray[np.float32],
|
|
78
|
+
radii: npt.NDArray[np.float32],
|
|
79
|
+
colors: npt.NDArray[np.float32],
|
|
80
|
+
border_colors: Optional[npt.NDArray[np.float32]] = None,
|
|
81
|
+
border_widths: Optional[npt.NDArray[np.float32]] = None,
|
|
82
|
+
antialiasing: float = 1.0
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Add multiple circles efficiently using numpy arrays (10-50x faster than loop)."""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
def add_rect(
|
|
88
|
+
self,
|
|
89
|
+
center: VectorType,
|
|
90
|
+
size: VectorType,
|
|
91
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
92
|
+
corner_radius: float = 0.0,
|
|
93
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
94
|
+
border_width: float = 0.0,
|
|
95
|
+
antialiasing: float = 1.0,
|
|
96
|
+
rotation: float = 0.0
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Add a rectangle instance to the batch."""
|
|
99
|
+
...
|
|
100
|
+
|
|
101
|
+
def add_rects_numpy(
|
|
102
|
+
self,
|
|
103
|
+
centers: npt.NDArray[np.float32],
|
|
104
|
+
sizes: npt.NDArray[np.float32],
|
|
105
|
+
colors: npt.NDArray[np.float32],
|
|
106
|
+
corner_radii: Optional[npt.NDArray[np.float32]] = None,
|
|
107
|
+
border_colors: Optional[npt.NDArray[np.float32]] = None,
|
|
108
|
+
border_widths: Optional[npt.NDArray[np.float32]] = None,
|
|
109
|
+
antialiasing: float = 1.0,
|
|
110
|
+
rotations: Optional[npt.NDArray[np.float32]] = None
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Add multiple rectangles efficiently using numpy arrays."""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
def add_line(
|
|
116
|
+
self,
|
|
117
|
+
start: VectorType,
|
|
118
|
+
end: VectorType,
|
|
119
|
+
width: float = 1.0,
|
|
120
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0)
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Add a line instance to the batch."""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
def add_lines_numpy(
|
|
126
|
+
self,
|
|
127
|
+
starts: npt.NDArray[np.float32],
|
|
128
|
+
ends: npt.NDArray[np.float32],
|
|
129
|
+
widths: npt.NDArray[np.float32],
|
|
130
|
+
colors: npt.NDArray[np.float32]
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Add multiple lines efficiently using numpy arrays."""
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
def flush(self) -> None:
|
|
136
|
+
"""Draw all instances in a single draw call."""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
def clear(self) -> None:
|
|
140
|
+
"""Clear the batch without drawing."""
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
class ShapeRenderer:
|
|
144
|
+
"""
|
|
145
|
+
High-performance 2D shape renderer using SDF (Signed Distance Functions) and GPU shaders.
|
|
146
|
+
Supports immediate mode, cached drawing, and batched rendering.
|
|
147
|
+
"""
|
|
148
|
+
ctx: ContextType
|
|
149
|
+
circle_instanced_prog: ProgramType
|
|
150
|
+
rect_instanced_prog: ProgramType
|
|
151
|
+
line_instanced_prog: ProgramType
|
|
152
|
+
circle_prog: ProgramType
|
|
153
|
+
rect_prog: ProgramType
|
|
154
|
+
line_prog: ProgramType
|
|
155
|
+
circle_vbo: BufferType
|
|
156
|
+
rect_vbo: BufferType
|
|
157
|
+
line_vbo: BufferType
|
|
158
|
+
circle_vao: VAOType
|
|
159
|
+
rect_vao: VAOType
|
|
160
|
+
line_vao: VAOType
|
|
161
|
+
|
|
162
|
+
def __init__(self, ctx: ContextType) -> None: ...
|
|
163
|
+
|
|
164
|
+
def draw_circle(
|
|
165
|
+
self,
|
|
166
|
+
center: VectorType,
|
|
167
|
+
radius: float,
|
|
168
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
169
|
+
rotation: float = 0.0,
|
|
170
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
171
|
+
border_width: float = 0.0,
|
|
172
|
+
antialiasing: float = 1.0
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Draw a circle immediately."""
|
|
175
|
+
...
|
|
176
|
+
|
|
177
|
+
def create_circle(
|
|
178
|
+
self,
|
|
179
|
+
center: VectorType,
|
|
180
|
+
radius: float,
|
|
181
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
182
|
+
rotation: float = 0.0,
|
|
183
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
184
|
+
border_width: float = 0.0,
|
|
185
|
+
antialiasing: float = 1.0
|
|
186
|
+
) -> ShapeLabel:
|
|
187
|
+
"""Create a cached circle for repeated drawing."""
|
|
188
|
+
...
|
|
189
|
+
|
|
190
|
+
def draw_rect(
|
|
191
|
+
self,
|
|
192
|
+
position: VectorType,
|
|
193
|
+
size: VectorType,
|
|
194
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
195
|
+
rotation: float = 0.0,
|
|
196
|
+
corner_radius: float = 0.0,
|
|
197
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
198
|
+
border_width: float = 0.0,
|
|
199
|
+
antialiasing: float = 1.0
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Draw a rectangle immediately."""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
def create_rect(
|
|
205
|
+
self,
|
|
206
|
+
position: VectorType,
|
|
207
|
+
size: VectorType,
|
|
208
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
209
|
+
rotation: float = 0.0,
|
|
210
|
+
corner_radius: float = 0.0,
|
|
211
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
212
|
+
border_width: float = 0.0,
|
|
213
|
+
antialiasing: float = 1.0
|
|
214
|
+
) -> ShapeLabel:
|
|
215
|
+
"""Create a cached rectangle for repeated drawing."""
|
|
216
|
+
...
|
|
217
|
+
|
|
218
|
+
def draw_line(
|
|
219
|
+
self,
|
|
220
|
+
start: VectorType,
|
|
221
|
+
end: VectorType,
|
|
222
|
+
width: float = 1.0,
|
|
223
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
224
|
+
antialiasing: float = 1.0
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Draw a single line segment."""
|
|
227
|
+
...
|
|
228
|
+
|
|
229
|
+
def draw_lines(
|
|
230
|
+
self,
|
|
231
|
+
points: npt.NDArray[np.float32] | Sequence[VectorType],
|
|
232
|
+
width: float = 1.0,
|
|
233
|
+
color: ColorType | npt.NDArray[np.float32] = (1.0, 1.0, 1.0, 1.0),
|
|
234
|
+
antialiasing: float = 1.0,
|
|
235
|
+
closed: bool = False
|
|
236
|
+
) -> None:
|
|
237
|
+
"""Draw a polyline (connected line segments)."""
|
|
238
|
+
...
|
|
239
|
+
|
|
240
|
+
def create_line(
|
|
241
|
+
self,
|
|
242
|
+
start: VectorType,
|
|
243
|
+
end: VectorType,
|
|
244
|
+
width: float = 1.0,
|
|
245
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
246
|
+
antialiasing: float = 1.0
|
|
247
|
+
) -> ShapeLabel:
|
|
248
|
+
"""Create a cached line for repeated drawing."""
|
|
249
|
+
...
|
|
250
|
+
|
|
251
|
+
def create_lines(
|
|
252
|
+
self,
|
|
253
|
+
points: npt.NDArray[np.float32] | Sequence[VectorType],
|
|
254
|
+
width: float = 1.0,
|
|
255
|
+
color: ColorType | npt.NDArray[np.float32] = (1.0, 1.0, 1.0, 1.0),
|
|
256
|
+
antialiasing: float = 1.0,
|
|
257
|
+
closed: bool = False
|
|
258
|
+
) -> ShapeLabel:
|
|
259
|
+
"""Create a cached polyline for repeated drawing."""
|
|
260
|
+
...
|
|
261
|
+
|
|
262
|
+
def create_circle_batch(self, max_shapes: int = 10000) -> InstancedShapeBatch:
|
|
263
|
+
"""Create a batch for drawing multiple circles efficiently using GPU instancing."""
|
|
264
|
+
...
|
|
265
|
+
|
|
266
|
+
def create_rect_batch(self, max_shapes: int = 10000) -> InstancedShapeBatch:
|
|
267
|
+
"""Create a batch for drawing multiple rectangles efficiently using GPU instancing."""
|
|
268
|
+
...
|
|
269
|
+
|
|
270
|
+
def create_line_batch(self, max_shapes: int = 10000) -> InstancedShapeBatch:
|
|
271
|
+
"""Create a batch for drawing multiple lines efficiently using GPU instancing."""
|
|
272
|
+
...
|
e2D/test_colors.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test script for new e2D color system
|
|
3
|
+
Demonstrates usage and verifies functionality
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Test basic imports - use direct imports to avoid types.py conflict
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
10
|
+
|
|
11
|
+
from colors import Color, normalize_color, lerp_colors, gradient
|
|
12
|
+
from color_defs import RED, BLUE, GREEN, ORANGE, get_color, MD_BLUE
|
|
13
|
+
|
|
14
|
+
print("=" * 60)
|
|
15
|
+
print("e2D Color System Test")
|
|
16
|
+
print("=" * 60)
|
|
17
|
+
|
|
18
|
+
# Test 1: Creating colors
|
|
19
|
+
print("\n1. Creating Colors:")
|
|
20
|
+
c1 = Color(1.0, 0.0, 0.0, 1.0) # Red
|
|
21
|
+
print(f" Color(1, 0, 0, 1) = {c1}")
|
|
22
|
+
|
|
23
|
+
c2 = Color.from_rgb255(255, 128, 0) # Orange
|
|
24
|
+
print(f" from_rgb255(255, 128, 0) = {c2}")
|
|
25
|
+
|
|
26
|
+
c3 = Color.from_hex("#00FF00") # Green
|
|
27
|
+
print(f" from_hex('#00FF00') = {c3}")
|
|
28
|
+
|
|
29
|
+
c4 = Color.from_hsv(0.5, 1.0, 1.0) # Cyan
|
|
30
|
+
print(f" from_hsv(0.5, 1, 1) = {c4}")
|
|
31
|
+
|
|
32
|
+
# Test 2: Color operations
|
|
33
|
+
print("\n2. Color Operations:")
|
|
34
|
+
red = Color.red()
|
|
35
|
+
blue = Color.blue()
|
|
36
|
+
print(f" red.lerp(blue, 0.5) = {red.lerp(blue, 0.5)}")
|
|
37
|
+
print(f" red.lighten(0.2) = {red.lighten(0.2)}")
|
|
38
|
+
print(f" blue.darken(0.3) = {blue.darken(0.3)}")
|
|
39
|
+
print(f" red.invert() = {red.invert()}")
|
|
40
|
+
|
|
41
|
+
# Test 3: Color space conversions
|
|
42
|
+
print("\n3. Color Space Conversions:")
|
|
43
|
+
orange = Color.orange()
|
|
44
|
+
print(f" Orange RGB: {orange.to_rgb()}")
|
|
45
|
+
print(f" Orange RGB255: {orange.to_rgb255()}")
|
|
46
|
+
print(f" Orange HSV: {orange.to_hsv()}")
|
|
47
|
+
print(f" Orange HEX: {orange.to_hex()}")
|
|
48
|
+
|
|
49
|
+
# Test 4: Pre-defined colors
|
|
50
|
+
print("\n4. Pre-defined Colors:")
|
|
51
|
+
print(f" RED = {RED}")
|
|
52
|
+
print(f" BLUE = {BLUE}")
|
|
53
|
+
print(f" MD_BLUE = {MD_BLUE}")
|
|
54
|
+
print(f" get_color('orange') = {get_color('orange')}")
|
|
55
|
+
|
|
56
|
+
# Test 5: Utility functions
|
|
57
|
+
print("\n5. Utility Functions:")
|
|
58
|
+
normalized = normalize_color("#FF0000")
|
|
59
|
+
print(f" normalize_color('#FF0000') = {normalized}")
|
|
60
|
+
|
|
61
|
+
lerped = lerp_colors(RED, BLUE, 0.5)
|
|
62
|
+
print(f" lerp_colors(RED, BLUE, 0.5) = {lerped}")
|
|
63
|
+
|
|
64
|
+
grad = gradient([RED, GREEN, BLUE], 5)
|
|
65
|
+
print(f" gradient([RED, GREEN, BLUE], 5):")
|
|
66
|
+
for i, color in enumerate(grad):
|
|
67
|
+
print(f" [{i}] = {color}")
|
|
68
|
+
|
|
69
|
+
# Test 6: Immutability and tuple unpacking
|
|
70
|
+
print("\n6. Immutability & Unpacking:")
|
|
71
|
+
c = Color.red()
|
|
72
|
+
r, g, b, a = c
|
|
73
|
+
print(f" Unpacked red: r={r}, g={g}, b={b}, a={a}")
|
|
74
|
+
print(f" Indexing: c[0]={c[0]}, c[1]={c[1]}, c[2]={c[2]}, c[3]={c[3]}")
|
|
75
|
+
|
|
76
|
+
# Test 7: Operators
|
|
77
|
+
print("\n7. Operators:")
|
|
78
|
+
c1 = Color(0.5, 0.3, 0.2, 1.0)
|
|
79
|
+
c2 = Color(0.2, 0.4, 0.5, 1.0)
|
|
80
|
+
print(f" c1 = {c1}")
|
|
81
|
+
print(f" c2 = {c2}")
|
|
82
|
+
print(f" c1 + c2 = {c1 + c2}")
|
|
83
|
+
print(f" c1 - c2 = {c1 - c2}")
|
|
84
|
+
print(f" c1 * 2.0 = {c1 * 2.0}")
|
|
85
|
+
print(f" c1 / 2.0 = {c1 / 2.0}")
|
|
86
|
+
|
|
87
|
+
# Test 8: GPU compatibility
|
|
88
|
+
print("\n8. GPU Compatibility:")
|
|
89
|
+
c = Color.from_rgba(0.8, 0.4, 0.2, 1.0)
|
|
90
|
+
arr = c.to_array()
|
|
91
|
+
print(f" Color as numpy array: {arr}")
|
|
92
|
+
print(f" Array dtype: {arr.dtype}")
|
|
93
|
+
print(f" Array shape: {arr.shape}")
|
|
94
|
+
|
|
95
|
+
# Test 9: Color manipulation
|
|
96
|
+
print("\n9. Color Manipulation:")
|
|
97
|
+
green = Color.green()
|
|
98
|
+
print(f" Green: {green}")
|
|
99
|
+
print(f" Rotate hue +60°: {green.rotate_hue(60)}")
|
|
100
|
+
print(f" Rotate hue +120°: {green.rotate_hue(120)}")
|
|
101
|
+
print(f" Saturate +0.3: {green.saturate(0.3)}")
|
|
102
|
+
print(f" Desaturate -0.5: {green.desaturate(0.5)}")
|
|
103
|
+
print(f" Grayscale: {green.grayscale()}")
|
|
104
|
+
|
|
105
|
+
# Test 10: with_alpha
|
|
106
|
+
print("\n10. Alpha Manipulation:")
|
|
107
|
+
c = Color.red()
|
|
108
|
+
print(f" Red (full opacity): {c}")
|
|
109
|
+
print(f" Red with 50% alpha: {c.with_alpha(0.5)}")
|
|
110
|
+
print(f" Red transparent: {c.with_alpha(0.0)}")
|
|
111
|
+
|
|
112
|
+
print("\n" + "=" * 60)
|
|
113
|
+
print("All tests completed successfully!")
|
|
114
|
+
print("=" * 60)
|
|
115
|
+
|
|
116
|
+
# Summary
|
|
117
|
+
print("\n✓ Color class is immutable and GPU-optimized")
|
|
118
|
+
print("✓ Default format: RGBA floats (0.0-1.0)")
|
|
119
|
+
print("✓ Full ModernGL/GLTF compatibility")
|
|
120
|
+
print("✓ Rich color operations and conversions")
|
|
121
|
+
print("✓ 80+ pre-defined colors available")
|
|
122
|
+
print("✓ Type-safe with proper type hints")
|
e2D/text_renderer.py
CHANGED
|
@@ -4,15 +4,18 @@ from PIL import Image, ImageFont
|
|
|
4
4
|
from attr import dataclass
|
|
5
5
|
import numpy as np
|
|
6
6
|
import moderngl
|
|
7
|
+
from .types import ColorType, VAOType, VectorType, ContextType, ProgramType, BufferType, TextureType
|
|
8
|
+
from .colors import normalize_color
|
|
9
|
+
from .color_defs import WHITE, BLACK
|
|
7
10
|
|
|
8
11
|
@dataclass
|
|
9
12
|
class TextStyle:
|
|
10
13
|
font: str = "arial.ttf"
|
|
11
14
|
font_size: int = 32
|
|
12
|
-
color:
|
|
13
|
-
bg_color:
|
|
14
|
-
bg_margin: float | tuple[float, float, float, float] = 15.0
|
|
15
|
-
bg_border_radius: float | tuple[float, float, float, float] = 15.0
|
|
15
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0)
|
|
16
|
+
bg_color: ColorType = (0.0, 0.0, 0.0, 0.9)
|
|
17
|
+
bg_margin: float | tuple[float, float, float, float] | tuple[float, float] | list[float] = 15.0
|
|
18
|
+
bg_border_radius: float | tuple[float, float, float, float] | tuple[float, float] | list[float] = 15.0
|
|
16
19
|
|
|
17
20
|
class Pivots(Enum):
|
|
18
21
|
TOP_LEFT = 0
|
|
@@ -28,8 +31,19 @@ class Pivots(Enum):
|
|
|
28
31
|
DEFAULT_TEXT_STYLE = TextStyle()
|
|
29
32
|
|
|
30
33
|
class TextLabel:
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
ctx: ContextType
|
|
35
|
+
prog: ProgramType
|
|
36
|
+
texture: TextureType
|
|
37
|
+
vertices: list[float]
|
|
38
|
+
vbo: BufferType
|
|
39
|
+
vao: VAOType
|
|
40
|
+
bg_prog: Optional[ProgramType]
|
|
41
|
+
bg_vertices: Optional[list[float]]
|
|
42
|
+
bg_vbo: Optional[BufferType]
|
|
43
|
+
bg_vao: Optional[VAOType]
|
|
44
|
+
|
|
45
|
+
def __init__(self, ctx: ContextType, prog: ProgramType, texture: TextureType, vertices: list[float],
|
|
46
|
+
bg_prog: Optional[ProgramType] = None, bg_vertices: Optional[list[float]] = None) -> None:
|
|
33
47
|
"""
|
|
34
48
|
A pre-rendered text label for efficient drawing.
|
|
35
49
|
To generate select a option below:
|
|
@@ -75,7 +89,13 @@ class TextRenderer:
|
|
|
75
89
|
Renders text using a texture atlas generated from a TTF font via Pillow.
|
|
76
90
|
Supports multiple fonts and sizes with caching for optimization.
|
|
77
91
|
"""
|
|
78
|
-
|
|
92
|
+
ctx: ContextType
|
|
93
|
+
font_cache: dict[tuple[str, int], dict]
|
|
94
|
+
chars: str
|
|
95
|
+
bg_prog: ProgramType
|
|
96
|
+
prog: ProgramType
|
|
97
|
+
|
|
98
|
+
def __init__(self, ctx: ContextType) -> None:
|
|
79
99
|
self.ctx = ctx
|
|
80
100
|
|
|
81
101
|
# Cache for font atlases: (font_path, font_size) -> {font, char_data, texture}
|
|
@@ -281,20 +301,30 @@ class TextRenderer:
|
|
|
281
301
|
|
|
282
302
|
return total_w, line_height
|
|
283
303
|
|
|
284
|
-
def _normalize_margin(self, margin: float | tuple[float, float, float, float]) -> tuple[float, float, float, float]:
|
|
304
|
+
def _normalize_margin(self, margin: float | tuple[float, float, float, float] | tuple[float, float] | list[float]) -> tuple[float, float, float, float]:
|
|
285
305
|
"""Normalize margin to (top, right, bottom, left)."""
|
|
286
306
|
if isinstance(margin, (int, float)):
|
|
287
307
|
return (margin, margin, margin, margin)
|
|
288
|
-
|
|
308
|
+
elif isinstance(margin, (list, tuple)):
|
|
309
|
+
if len(margin) == 2:
|
|
310
|
+
return (margin[0], margin[1], margin[0], margin[1])
|
|
311
|
+
elif len(margin) == 4:
|
|
312
|
+
return (margin[0], margin[1], margin[2], margin[3])
|
|
313
|
+
return margin[0], margin[1], margin[2], margin[3]
|
|
289
314
|
|
|
290
|
-
def _normalize_radius(self, radius: float | tuple[float, float, float, float]) -> tuple[float, float, float, float]:
|
|
315
|
+
def _normalize_radius(self, radius: float | tuple[float, float, float, float] | tuple[float, float] | list[float]) -> tuple[float, float, float, float]:
|
|
291
316
|
"""Normalize border radius to (top-left, top-right, bottom-right, bottom-left)."""
|
|
292
317
|
if isinstance(radius, (int, float)):
|
|
293
318
|
return (radius, radius, radius, radius)
|
|
294
|
-
|
|
319
|
+
elif isinstance(radius, (list, tuple)):
|
|
320
|
+
if len(radius) == 2:
|
|
321
|
+
return (radius[0], radius[1], radius[0], radius[1])
|
|
322
|
+
elif len(radius) == 4:
|
|
323
|
+
return (radius[0], radius[1], radius[2], radius[3])
|
|
324
|
+
return radius[0], radius[1], radius[2], radius[3]
|
|
295
325
|
|
|
296
326
|
def _generate_background_vertices(self, x: float, y: float, width: float, height: float,
|
|
297
|
-
bg_color:
|
|
327
|
+
bg_color: ColorType, margin: tuple[float, float, float, float],
|
|
298
328
|
radius: tuple[float, float, float, float]) -> list[float]:
|
|
299
329
|
"""Generate vertices for a rounded rectangle background."""
|
|
300
330
|
# Apply margin
|
|
@@ -321,8 +351,8 @@ class TextRenderer:
|
|
|
321
351
|
|
|
322
352
|
return vertices
|
|
323
353
|
|
|
324
|
-
def _generate_vertices(self, text: str, pos:
|
|
325
|
-
color:
|
|
354
|
+
def _generate_vertices(self, text: str, pos: VectorType, scale: float = 1.0,
|
|
355
|
+
color: ColorType = WHITE, pivot: Pivots | int = Pivots.TOP_LEFT, char_data: dict = {} ) -> list[float]:
|
|
326
356
|
|
|
327
357
|
if not char_data:
|
|
328
358
|
raise ValueError("char_data is required for _generate_vertices")
|
|
@@ -396,7 +426,7 @@ class TextRenderer:
|
|
|
396
426
|
|
|
397
427
|
return vertices
|
|
398
428
|
|
|
399
|
-
def draw_text(self, text: str, pos:
|
|
429
|
+
def draw_text(self, text: str, pos: VectorType, scale: float = 1.0, style: TextStyle = DEFAULT_TEXT_STYLE, pivot: Pivots | int = Pivots.TOP_LEFT) -> None:
|
|
400
430
|
if not text:
|
|
401
431
|
return
|
|
402
432
|
|
|
@@ -451,7 +481,7 @@ class TextRenderer:
|
|
|
451
481
|
self.ctx.enable(moderngl.BLEND)
|
|
452
482
|
self.vao.render(moderngl.TRIANGLES, vertices=len(vertices)//8)
|
|
453
483
|
|
|
454
|
-
def create_label(self, text: str, x: float, y: float, scale: float = 1.0, style: TextStyle = DEFAULT_TEXT_STYLE, pivot: Pivots = Pivots.TOP_LEFT) -> TextLabel:
|
|
484
|
+
def create_label(self, text: str, x: float, y: float, scale: float = 1.0, style: TextStyle = DEFAULT_TEXT_STYLE, pivot: Pivots | int = Pivots.TOP_LEFT) -> TextLabel:
|
|
455
485
|
if not text:
|
|
456
486
|
# Return empty label with default texture
|
|
457
487
|
font_atlas = self._get_or_create_font_atlas(style.font, style.font_size)
|
e2D/text_renderer.pyi
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type stubs for text_renderer module
|
|
3
|
+
Text rendering using texture atlases and TTF fonts
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from .types import ColorType, ContextType, ProgramType, TextureType, VAOType, VectorType, BufferType
|
|
9
|
+
from .color_defs import WHITE
|
|
10
|
+
|
|
11
|
+
class TextStyle:
|
|
12
|
+
"""Text styling options"""
|
|
13
|
+
font: str
|
|
14
|
+
font_size: int
|
|
15
|
+
color: ColorType
|
|
16
|
+
bg_color: ColorType
|
|
17
|
+
bg_margin: float | tuple[float, float, float, float]
|
|
18
|
+
bg_border_radius: float | tuple[float, float, float, float]
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
font: str = "arial.ttf",
|
|
23
|
+
font_size: int = 32,
|
|
24
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
25
|
+
bg_color: ColorType = (0.0, 0.0, 0.0, 0.9),
|
|
26
|
+
bg_margin: float | tuple[float, float, float, float] = 15.0,
|
|
27
|
+
bg_border_radius: float | tuple[float, float, float, float] = 15.0
|
|
28
|
+
) -> None: ...
|
|
29
|
+
|
|
30
|
+
class Pivots(Enum):
|
|
31
|
+
"""Text anchor/pivot points"""
|
|
32
|
+
TOP_LEFT: int
|
|
33
|
+
TOP_MIDDLE: int
|
|
34
|
+
TOP_RIGHT: int
|
|
35
|
+
LEFT: int
|
|
36
|
+
CENTER: int
|
|
37
|
+
RIGHT: int
|
|
38
|
+
BOTTOM_LEFT: int
|
|
39
|
+
BOTTOM_MIDDLE: int
|
|
40
|
+
BOTTOM_RIGHT: int
|
|
41
|
+
|
|
42
|
+
DEFAULT_TEXT_STYLE: TextStyle
|
|
43
|
+
|
|
44
|
+
class TextLabel:
|
|
45
|
+
"""A pre-rendered text label for efficient drawing."""
|
|
46
|
+
ctx: ContextType
|
|
47
|
+
prog: ProgramType
|
|
48
|
+
texture: TextureType
|
|
49
|
+
vertices: list[float]
|
|
50
|
+
vbo: BufferType
|
|
51
|
+
vao: VAOType
|
|
52
|
+
bg_prog: Optional[ProgramType]
|
|
53
|
+
bg_vertices: Optional[list[float]]
|
|
54
|
+
bg_vbo: Optional[BufferType]
|
|
55
|
+
bg_vao: Optional[VAOType]
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
ctx: ContextType,
|
|
60
|
+
prog: ProgramType,
|
|
61
|
+
texture: TextureType,
|
|
62
|
+
vertices: list[float],
|
|
63
|
+
bg_prog: Optional[ProgramType] = None,
|
|
64
|
+
bg_vertices: Optional[list[float]] = None
|
|
65
|
+
) -> None: ...
|
|
66
|
+
|
|
67
|
+
def draw(self) -> None:
|
|
68
|
+
"""Draw the text label"""
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
class TextRenderer:
|
|
72
|
+
"""
|
|
73
|
+
Renders text using a texture atlas generated from a TTF font via Pillow.
|
|
74
|
+
Supports multiple fonts and sizes with caching for optimization.
|
|
75
|
+
"""
|
|
76
|
+
ctx: ContextType
|
|
77
|
+
font_cache: dict[tuple[str, int], dict]
|
|
78
|
+
chars: str
|
|
79
|
+
bg_prog: ProgramType
|
|
80
|
+
bg_vbo: BufferType
|
|
81
|
+
bg_vao: VAOType
|
|
82
|
+
prog: ProgramType
|
|
83
|
+
vbo: BufferType
|
|
84
|
+
vao: VAOType
|
|
85
|
+
|
|
86
|
+
def __init__(self, ctx: ContextType) -> None: ...
|
|
87
|
+
|
|
88
|
+
def get_text_width(
|
|
89
|
+
self,
|
|
90
|
+
text: str,
|
|
91
|
+
scale: float = 1.0,
|
|
92
|
+
style: TextStyle = DEFAULT_TEXT_STYLE
|
|
93
|
+
) -> float:
|
|
94
|
+
"""Calculate the width of the text."""
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
def draw_text(
|
|
98
|
+
self,
|
|
99
|
+
text: str,
|
|
100
|
+
pos: VectorType,
|
|
101
|
+
scale: float = 1.0,
|
|
102
|
+
style: TextStyle = DEFAULT_TEXT_STYLE,
|
|
103
|
+
pivot: Pivots | int = Pivots.TOP_LEFT
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Draw text immediately."""
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
def create_label(
|
|
109
|
+
self,
|
|
110
|
+
text: str,
|
|
111
|
+
x: float,
|
|
112
|
+
y: float,
|
|
113
|
+
scale: float = 1.0,
|
|
114
|
+
style: TextStyle = DEFAULT_TEXT_STYLE,
|
|
115
|
+
pivot: Pivots | int = Pivots.TOP_LEFT
|
|
116
|
+
) -> TextLabel:
|
|
117
|
+
"""Create a cached text label for repeated drawing."""
|
|
118
|
+
...
|
e2D/types.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type definitions for e2D library
|
|
3
|
+
Provides common type aliases and type hints used throughout the library
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
# Simple type aliases without imports to avoid circular dependencies
|
|
9
|
+
# These are used for type hints only
|
|
10
|
+
|
|
11
|
+
# Type alias for vector-like objects
|
|
12
|
+
# At runtime, accepts: Vector2D instances, tuples of 2 numbers, or sequences of 2 numbers
|
|
13
|
+
# Use this for ALL vector/position/size parameters for maximum flexibility
|
|
14
|
+
VectorType = object # Union at type-check time via .pyi file
|
|
15
|
+
|
|
16
|
+
# Color type (RGBA values between 0.0 and 1.0)
|
|
17
|
+
ColorType = tuple[float, float, float, float]
|
|
18
|
+
|
|
19
|
+
# Numeric types
|
|
20
|
+
Number = int | float
|
|
21
|
+
IntVec2 = tuple[int, int]
|
|
22
|
+
FloatVec2 = tuple[float, float]
|
|
23
|
+
NumVec2 = tuple[Number, Number]
|
|
24
|
+
|
|
25
|
+
# Array-like types for numpy and buffers
|
|
26
|
+
pArray = object # list | tuple at runtime
|
|
27
|
+
ArrayLike = object # numpy array or similar at runtime
|
|
28
|
+
|
|
29
|
+
# Shader and GPU resource types (forward declarations at runtime)
|
|
30
|
+
ContextType = object # moderngl.Context
|
|
31
|
+
ProgramType = object # moderngl.Program
|
|
32
|
+
ComputeShaderType = object # moderngl.ComputeShader
|
|
33
|
+
BufferType = object # moderngl.Buffer
|
|
34
|
+
VAOType = object # moderngl.VertexArray
|
|
35
|
+
TextureType = object # moderngl.Texture
|
|
36
|
+
|
|
37
|
+
ProgramAttrType = object # moderngl.Uniform | moderngl.UniformBlock | moderngl.Attribute | moderngl.Varying
|
|
38
|
+
UniformType = object # moderngl.Uniform
|
|
39
|
+
UniformBlockType = object # moderngl.UniformBlock
|
|
40
|
+
|
|
41
|
+
# Window and input types
|
|
42
|
+
WindowType = object # glfw window
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
'VectorType',
|
|
46
|
+
'ColorType',
|
|
47
|
+
'Number',
|
|
48
|
+
'IntVec2',
|
|
49
|
+
'FloatVec2',
|
|
50
|
+
'NumVec2',
|
|
51
|
+
'ArrayLike',
|
|
52
|
+
'ContextType',
|
|
53
|
+
'ProgramType',
|
|
54
|
+
'BufferType',
|
|
55
|
+
'VAOType',
|
|
56
|
+
'TextureType',
|
|
57
|
+
'WindowType',
|
|
58
|
+
]
|