e2D 2.0.3__tar.gz → 2.1.0__tar.gz
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-2.0.3 → e2d-2.1.0}/DEVELOPER_GUIDE.md +4 -33
- {e2d-2.0.3/e2D.egg-info → e2d-2.1.0}/PKG-INFO +22 -4
- {e2d-2.0.3 → e2d-2.1.0}/README.md +21 -3
- {e2d-2.0.3 → e2d-2.1.0}/e2D/__init__.py +180 -70
- {e2d-2.0.3 → e2d-2.1.0}/e2D/ccolors.c +152 -152
- {e2d-2.0.3 → e2d-2.1.0}/e2D/cvectors.c +1807 -872
- {e2d-2.0.3 → e2d-2.1.0}/e2D/cvectors.pyi +6 -1
- {e2d-2.0.3 → e2d-2.1.0}/e2D/cvectors.pyx +44 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/devices.py +15 -14
- {e2d-2.0.3 → e2d-2.1.0}/e2D/plots.py +8 -7
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shapes.py +20 -19
- {e2d-2.0.3 → e2d-2.1.0}/e2D/text_renderer.py +15 -7
- {e2d-2.0.3 → e2d-2.1.0}/e2D/types.py +1 -7
- {e2d-2.0.3 → e2d-2.1.0}/e2D/types.pyi +0 -5
- e2d-2.1.0/e2D/vectors.py +189 -0
- {e2d-2.0.3 → e2d-2.1.0/e2D.egg-info}/PKG-INFO +22 -4
- {e2d-2.0.3 → e2d-2.1.0}/e2D.egg-info/SOURCES.txt +0 -9
- {e2d-2.0.3 → e2d-2.1.0}/setup.cfg +1 -1
- e2d-2.0.3/e2D/__init__.pyi +0 -145
- e2d-2.0.3/e2D/colors.pyi +0 -104
- e2d-2.0.3/e2D/commons.pyi +0 -79
- e2d-2.0.3/e2D/devices.pyi +0 -65
- e2d-2.0.3/e2D/plots.pyi +0 -238
- e2d-2.0.3/e2D/shapes.pyi +0 -272
- e2d-2.0.3/e2D/text_renderer.pyi +0 -118
- e2d-2.0.3/e2D/vectors.py +0 -339
- e2d-2.0.3/e2D/vectors.pyi +0 -106
- e2d-2.0.3/e2D/winrec.pyi +0 -87
- {e2d-2.0.3 → e2d-2.1.0}/LICENSE +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/MANIFEST.in +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/docs/API_REFERENCE.md +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/ccolors.pyi +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/ccolors.pyx +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/color_defs.py +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/colors.py +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/commons.py +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/cvectors.pxd +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/curve_fragment.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/curve_vertex.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/line_instanced_vertex.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/plot_grid_fragment.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/plot_grid_vertex.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/segment_fragment.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/segment_vertex.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/stream_fragment.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/stream_shift_compute.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/shaders/stream_vertex.glsl +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/test_colors.py +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D/winrec.py +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D.egg-info/dependency_links.txt +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D.egg-info/not-zip-safe +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D.egg-info/requires.txt +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/e2D.egg-info/top_level.txt +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/pyproject.toml +0 -0
- {e2d-2.0.3 → e2d-2.1.0}/setup.py +0 -0
|
@@ -62,12 +62,10 @@ e2D_2.0/
|
|
|
62
62
|
├── PUBLISHING.md # GitHub Actions publishing guide
|
|
63
63
|
├── LICENSE # MIT License
|
|
64
64
|
├── new_version.py # Version updater helper script
|
|
65
|
-
|
|
66
|
-
├── build_dev.bat # Local development build script (optional)
|
|
67
|
-
└── SET_TOKEN.bat # Local PyPI token setup (optional, ignored by git)
|
|
65
|
+
└── build_dev.bat # Local development build script (optional)
|
|
68
66
|
```
|
|
69
67
|
|
|
70
|
-
**Note**: `.bat` files are optional local helpers.
|
|
68
|
+
**Note**: `.bat` files are optional local helpers.
|
|
71
69
|
|
|
72
70
|
## 🔧 Development Workflow
|
|
73
71
|
|
|
@@ -103,10 +101,6 @@ python setup.py build_ext --inplace
|
|
|
103
101
|
# Test basic import
|
|
104
102
|
python -c "import e2D; print(e2D.__version__)"
|
|
105
103
|
|
|
106
|
-
# Run examples
|
|
107
|
-
python examples/example_usage.py
|
|
108
|
-
python examples/compare_performance.py
|
|
109
|
-
|
|
110
104
|
# Test installation from scratch
|
|
111
105
|
pip uninstall e2D
|
|
112
106
|
pip install .
|
|
@@ -119,11 +113,6 @@ pip install .
|
|
|
119
113
|
python new_version.py
|
|
120
114
|
```
|
|
121
115
|
|
|
122
|
-
Or on Windows:
|
|
123
|
-
```cmd
|
|
124
|
-
new_version.bat
|
|
125
|
-
```
|
|
126
|
-
|
|
127
116
|
This updates:
|
|
128
117
|
- `setup.cfg` → `version`
|
|
129
118
|
- `e2D/__init__.py` → `__version__`
|
|
@@ -160,25 +149,8 @@ python -c "from e2D import Vector2D; v = Vector2D(1, 2); print(v)"
|
|
|
160
149
|
|
|
161
150
|
### 7. Publishing to PyPI
|
|
162
151
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
new_version.bat
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
This script:
|
|
169
|
-
1. Updates version
|
|
170
|
-
2. Cleans build artifacts
|
|
171
|
-
3. Builds distributions
|
|
172
|
-
4. Uploads to PyPI
|
|
173
|
-
|
|
174
|
-
#### Option B: Manual
|
|
175
|
-
```bash
|
|
176
|
-
# Upload to PyPI
|
|
177
|
-
python -m twine upload dist/*
|
|
178
|
-
|
|
179
|
-
# Or upload to TestPyPI first
|
|
180
|
-
python -m twine upload --repository testpypi dist/*
|
|
181
|
-
```
|
|
152
|
+
Just push everything in the main e2D folder to this git repo, create a new release and send request to me, it will automatically build it, debug back if errors are found, and if everything works fine it will create a Pypi deployment request to the repo authors (me).
|
|
153
|
+
No build necessary if not for local testing.
|
|
182
154
|
|
|
183
155
|
## 📋 Pre-Release Checklist
|
|
184
156
|
|
|
@@ -255,7 +227,6 @@ xcode-select --install
|
|
|
255
227
|
```python
|
|
256
228
|
import e2D
|
|
257
229
|
print(f"Version: {e2D.__version__}")
|
|
258
|
-
print(f"Compiled: {e2D._VECTORS_COMPILED}")
|
|
259
230
|
```
|
|
260
231
|
|
|
261
232
|
### Benchmarking
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: e2D
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: High-performance 2D graphics and math library with ultra-optimized vector operations
|
|
5
5
|
Home-page: https://github.com/marick-py/e2D
|
|
6
6
|
Author: Riccardo Mariani
|
|
@@ -126,6 +126,24 @@ pip install "e2D<2.0"
|
|
|
126
126
|
- attrs (required - for data structures)
|
|
127
127
|
- OpenCV-Python (optional, for recording - install with `[rec]` extra)
|
|
128
128
|
|
|
129
|
+
### Linux-Specific Setup (Fedora/RHEL/CentOS)
|
|
130
|
+
|
|
131
|
+
If you encounter OpenGL library errors on Fedora-based systems:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Install Mesa OpenGL libraries
|
|
135
|
+
sudo dnf install mesa-libGL mesa-libEGL
|
|
136
|
+
|
|
137
|
+
# Create symlinks (ModernGL needs unversioned .so files)
|
|
138
|
+
sudo ln -s /usr/lib64/libGL.so.1 /usr/lib64/libGL.so
|
|
139
|
+
sudo ln -s /usr/lib64/libEGL.so.1 /usr/lib64/libEGL.so
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Note:** On Debian/Ubuntu systems, these symlinks are usually created automatically. If you encounter similar issues:
|
|
143
|
+
```bash
|
|
144
|
+
sudo apt-get install libgl1-mesa-glx libegl1-mesa
|
|
145
|
+
```
|
|
146
|
+
|
|
129
147
|
## 🚀 Quick Start
|
|
130
148
|
|
|
131
149
|
### Optimized Vector Operations
|
|
@@ -160,6 +178,7 @@ pos_array = vectors_to_array(positions)
|
|
|
160
178
|
|
|
161
179
|
```python
|
|
162
180
|
from e2D import RootEnv, DefEnv
|
|
181
|
+
from e2D.vectors import V2
|
|
163
182
|
|
|
164
183
|
class MyApp(DefEnv):
|
|
165
184
|
def __init__(self) -> None:
|
|
@@ -174,7 +193,7 @@ class MyApp(DefEnv):
|
|
|
174
193
|
pass
|
|
175
194
|
|
|
176
195
|
# Initialize and run
|
|
177
|
-
rootEnv = RootEnv(window_size=(1920, 1080), target_fps=60)
|
|
196
|
+
rootEnv = RootEnv(window_size=V2(1920, 1080), target_fps=60)
|
|
178
197
|
rootEnv.init(MyApp())
|
|
179
198
|
|
|
180
199
|
# Optional: Enable screen recording
|
|
@@ -228,7 +247,6 @@ Perfect for:
|
|
|
228
247
|
|
|
229
248
|
- **[API Reference](docs/API_REFERENCE.md)** - Complete API documentation
|
|
230
249
|
- **[Developer Guide](DEVELOPER_GUIDE.md)** - Development workflow and contributing
|
|
231
|
-
- **[Publishing Guide](PUBLISHING.md)** - GitHub Actions automated publishing
|
|
232
250
|
|
|
233
251
|
## 🎯 Use Cases
|
|
234
252
|
|
|
@@ -315,7 +333,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
315
333
|
## 📈 Version History
|
|
316
334
|
|
|
317
335
|
### Version 2.x (ModernGL-based - Current)
|
|
318
|
-
- **2.0
|
|
336
|
+
- **2.1.0** (Current) - Bug fixes and documentation improvements
|
|
319
337
|
- **2.0.0** - Complete rewrite with ModernGL rendering, Cython-optimized vectors, modern color system, screen recording, removed pygame dependency
|
|
320
338
|
|
|
321
339
|
### Version 1.x (Pygame-based - Legacy)
|
|
@@ -76,6 +76,24 @@ pip install "e2D<2.0"
|
|
|
76
76
|
- attrs (required - for data structures)
|
|
77
77
|
- OpenCV-Python (optional, for recording - install with `[rec]` extra)
|
|
78
78
|
|
|
79
|
+
### Linux-Specific Setup (Fedora/RHEL/CentOS)
|
|
80
|
+
|
|
81
|
+
If you encounter OpenGL library errors on Fedora-based systems:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Install Mesa OpenGL libraries
|
|
85
|
+
sudo dnf install mesa-libGL mesa-libEGL
|
|
86
|
+
|
|
87
|
+
# Create symlinks (ModernGL needs unversioned .so files)
|
|
88
|
+
sudo ln -s /usr/lib64/libGL.so.1 /usr/lib64/libGL.so
|
|
89
|
+
sudo ln -s /usr/lib64/libEGL.so.1 /usr/lib64/libEGL.so
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Note:** On Debian/Ubuntu systems, these symlinks are usually created automatically. If you encounter similar issues:
|
|
93
|
+
```bash
|
|
94
|
+
sudo apt-get install libgl1-mesa-glx libegl1-mesa
|
|
95
|
+
```
|
|
96
|
+
|
|
79
97
|
## 🚀 Quick Start
|
|
80
98
|
|
|
81
99
|
### Optimized Vector Operations
|
|
@@ -110,6 +128,7 @@ pos_array = vectors_to_array(positions)
|
|
|
110
128
|
|
|
111
129
|
```python
|
|
112
130
|
from e2D import RootEnv, DefEnv
|
|
131
|
+
from e2D.vectors import V2
|
|
113
132
|
|
|
114
133
|
class MyApp(DefEnv):
|
|
115
134
|
def __init__(self) -> None:
|
|
@@ -124,7 +143,7 @@ class MyApp(DefEnv):
|
|
|
124
143
|
pass
|
|
125
144
|
|
|
126
145
|
# Initialize and run
|
|
127
|
-
rootEnv = RootEnv(window_size=(1920, 1080), target_fps=60)
|
|
146
|
+
rootEnv = RootEnv(window_size=V2(1920, 1080), target_fps=60)
|
|
128
147
|
rootEnv.init(MyApp())
|
|
129
148
|
|
|
130
149
|
# Optional: Enable screen recording
|
|
@@ -178,7 +197,6 @@ Perfect for:
|
|
|
178
197
|
|
|
179
198
|
- **[API Reference](docs/API_REFERENCE.md)** - Complete API documentation
|
|
180
199
|
- **[Developer Guide](DEVELOPER_GUIDE.md)** - Development workflow and contributing
|
|
181
|
-
- **[Publishing Guide](PUBLISHING.md)** - GitHub Actions automated publishing
|
|
182
200
|
|
|
183
201
|
## 🎯 Use Cases
|
|
184
202
|
|
|
@@ -265,7 +283,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
265
283
|
## 📈 Version History
|
|
266
284
|
|
|
267
285
|
### Version 2.x (ModernGL-based - Current)
|
|
268
|
-
- **2.0
|
|
286
|
+
- **2.1.0** (Current) - Bug fixes and documentation improvements
|
|
269
287
|
- **2.0.0** - Complete rewrite with ModernGL rendering, Cython-optimized vectors, modern color system, screen recording, removed pygame dependency
|
|
270
288
|
|
|
271
289
|
### Version 1.x (Pygame-based - Legacy)
|
|
@@ -6,7 +6,7 @@ Copyright (c) 2025 Riccardo Mariani
|
|
|
6
6
|
MIT License
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__version__ = "2.0
|
|
9
|
+
__version__ = "2.1.0"
|
|
10
10
|
__author__ = "Riccardo Mariani"
|
|
11
11
|
__email__ = "riccardo.mariani@emptyhead.dev"
|
|
12
12
|
|
|
@@ -18,12 +18,12 @@ import os
|
|
|
18
18
|
|
|
19
19
|
# Import type definitions
|
|
20
20
|
from .types import (
|
|
21
|
-
ComputeShaderType, ProgramAttrType, UniformType,
|
|
21
|
+
ComputeShaderType, ProgramAttrType, UniformType, ColorType, Number,
|
|
22
22
|
ContextType, ProgramType, BufferType, WindowType, pArray
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
# Import original e2D modules
|
|
26
|
-
from .text_renderer import
|
|
26
|
+
from .text_renderer import DEFAULT_16_TEXT_STYLE, MONO_16_TEXT_STYLE, Pivots, TextRenderer, TextLabel, TextStyle
|
|
27
27
|
from .shapes import ShapeRenderer, ShapeLabel, InstancedShapeBatch, FillMode
|
|
28
28
|
from .devices import Keyboard, Mouse, KeyState
|
|
29
29
|
from .commons import get_pattr, get_pattr_value, set_pattr_value, get_uniform
|
|
@@ -43,7 +43,7 @@ from .color_defs import (
|
|
|
43
43
|
|
|
44
44
|
# Try to import Cython-optimized color operations (optional batch utilities)
|
|
45
45
|
try:
|
|
46
|
-
from . import ccolors
|
|
46
|
+
from . import ccolors
|
|
47
47
|
_COLOR_COMPILED = True
|
|
48
48
|
except ImportError:
|
|
49
49
|
_COLOR_COMPILED = False
|
|
@@ -61,7 +61,6 @@ from .vectors import (
|
|
|
61
61
|
lerp,
|
|
62
62
|
create_grid,
|
|
63
63
|
create_circle,
|
|
64
|
-
_COMPILED as _VECTOR_COMPILED,
|
|
65
64
|
)
|
|
66
65
|
|
|
67
66
|
|
|
@@ -76,8 +75,9 @@ class DefEnv:
|
|
|
76
75
|
def on_resize(self, width: int, height: int) -> None: ...
|
|
77
76
|
|
|
78
77
|
class RootEnv:
|
|
79
|
-
window_size:
|
|
78
|
+
window_size: Vector2D
|
|
80
79
|
target_fps: int
|
|
80
|
+
draw_fps: bool
|
|
81
81
|
window: WindowType
|
|
82
82
|
ctx: ContextType
|
|
83
83
|
programs: dict[str, ProgramType]
|
|
@@ -94,11 +94,13 @@ class RootEnv:
|
|
|
94
94
|
|
|
95
95
|
def __init__(
|
|
96
96
|
self,
|
|
97
|
-
window_size:
|
|
97
|
+
window_size: Vector2D = V2(1920, 1080),
|
|
98
98
|
target_fps: int = 60,
|
|
99
|
-
vsync: bool =
|
|
99
|
+
vsync: bool = False,
|
|
100
|
+
resizable: bool = True,
|
|
100
101
|
version: tuple[int, int] = (4, 3),
|
|
101
|
-
monitor: Optional[int] = None
|
|
102
|
+
monitor: Optional[int] = None,
|
|
103
|
+
draw_fps: bool = False,
|
|
102
104
|
) -> None:
|
|
103
105
|
|
|
104
106
|
if not glfw.init():
|
|
@@ -108,15 +110,25 @@ class RootEnv:
|
|
|
108
110
|
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, version[1])
|
|
109
111
|
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
|
|
110
112
|
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
|
|
111
|
-
glfw.window_hint(glfw.RESIZABLE,
|
|
113
|
+
glfw.window_hint(glfw.RESIZABLE, resizable)
|
|
112
114
|
|
|
113
115
|
self.window_size = window_size
|
|
114
116
|
self.target_fps = target_fps
|
|
117
|
+
self.draw_fps = draw_fps
|
|
118
|
+
self._resizable = resizable
|
|
119
|
+
self._vsync = vsync
|
|
115
120
|
|
|
116
|
-
self.window = glfw.create_window(window_size[0], window_size[1], "e2D", monitor, None)
|
|
121
|
+
self.window = glfw.create_window(int(window_size[0]), int(window_size[1]), "e2D", monitor, None)
|
|
117
122
|
if not self.window:
|
|
118
123
|
glfw.terminate()
|
|
119
124
|
raise RuntimeError("Failed to create GLFW window")
|
|
125
|
+
|
|
126
|
+
# Center window on screen by default
|
|
127
|
+
if monitor is None:
|
|
128
|
+
video_mode = glfw.get_video_mode(glfw.get_primary_monitor())
|
|
129
|
+
window_w, window_h = int(window_size[0]), int(window_size[1])
|
|
130
|
+
screen_w, screen_h = video_mode.size.width, video_mode.size.height
|
|
131
|
+
glfw.set_window_pos(self.window, (screen_w - window_w) // 2, (screen_h - window_h) // 2)
|
|
120
132
|
|
|
121
133
|
glfw.make_context_current(self.window)
|
|
122
134
|
|
|
@@ -128,10 +140,7 @@ class RootEnv:
|
|
|
128
140
|
raise
|
|
129
141
|
|
|
130
142
|
# VSync control - must be set AFTER context creation
|
|
131
|
-
if vsync
|
|
132
|
-
glfw.swap_interval(1)
|
|
133
|
-
else:
|
|
134
|
-
glfw.swap_interval(0)
|
|
143
|
+
glfw.swap_interval(1 if vsync else 0)
|
|
135
144
|
|
|
136
145
|
print(f"OpenGL Context: {self.ctx.version_code} / {self.ctx.info['GL_RENDERER']}")
|
|
137
146
|
|
|
@@ -165,13 +174,88 @@ class RootEnv:
|
|
|
165
174
|
"""Get window size as floats for shader uniforms."""
|
|
166
175
|
return (float(self.window_size[0]), float(self.window_size[1]))
|
|
167
176
|
|
|
177
|
+
@property
|
|
178
|
+
def window_position(self) -> Vector2D:
|
|
179
|
+
"""Get current window position."""
|
|
180
|
+
pos = glfw.get_window_pos(self.window)
|
|
181
|
+
return V2(float(pos[0]), float(pos[1]))
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def resizable(self) -> bool:
|
|
185
|
+
"""Get window resizable state."""
|
|
186
|
+
return self._resizable
|
|
187
|
+
|
|
188
|
+
@resizable.setter
|
|
189
|
+
def resizable(self, value: bool) -> None:
|
|
190
|
+
"""Set window resizable state."""
|
|
191
|
+
self._resizable = value
|
|
192
|
+
glfw.set_window_attrib(self.window, glfw.RESIZABLE, glfw.TRUE if value else glfw.FALSE)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def vsync(self) -> bool:
|
|
196
|
+
"""Get VSync state."""
|
|
197
|
+
return self._vsync
|
|
198
|
+
|
|
199
|
+
@vsync.setter
|
|
200
|
+
def vsync(self, value: bool) -> None:
|
|
201
|
+
"""Set VSync state. Changes take effect immediately."""
|
|
202
|
+
self._vsync = value
|
|
203
|
+
glfw.swap_interval(1 if value else 0)
|
|
204
|
+
|
|
168
205
|
@property
|
|
169
206
|
def runtime(self) -> float:
|
|
170
207
|
"""Get total elapsed time since program initialization in seconds."""
|
|
171
208
|
return time.perf_counter() - self.start_time
|
|
172
209
|
|
|
210
|
+
def set_window_size(self, width: int | float, height: int | float) -> None:
|
|
211
|
+
"""Set window size. Use this instead of modifying window_size directly.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
width: New window width
|
|
215
|
+
height: New window height
|
|
216
|
+
"""
|
|
217
|
+
width, height = int(width), int(height)
|
|
218
|
+
glfw.set_window_size(self.window, width, height)
|
|
219
|
+
self.window_size.set(width, height)
|
|
220
|
+
|
|
221
|
+
def set_window_position(self, x: int | float, y: int | float) -> None:
|
|
222
|
+
"""Set window position on screen.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
x: X position (left edge)
|
|
226
|
+
y: Y position (top edge)
|
|
227
|
+
"""
|
|
228
|
+
glfw.set_window_pos(self.window, int(x), int(y))
|
|
229
|
+
|
|
230
|
+
def center_window(self) -> None:
|
|
231
|
+
"""Center the window on the primary monitor."""
|
|
232
|
+
video_mode = glfw.get_video_mode(glfw.get_primary_monitor())
|
|
233
|
+
window_w, window_h = int(self.window_size[0]), int(self.window_size[1])
|
|
234
|
+
screen_w, screen_h = video_mode.size.width, video_mode.size.height
|
|
235
|
+
self.set_window_position((screen_w - window_w) // 2, (screen_h - window_h) // 2)
|
|
236
|
+
|
|
237
|
+
def set_window_title(self, title: str) -> None:
|
|
238
|
+
"""Set window title.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
title: New window title
|
|
242
|
+
"""
|
|
243
|
+
glfw.set_window_title(self.window, title)
|
|
244
|
+
|
|
245
|
+
def maximize_window(self) -> None:
|
|
246
|
+
"""Maximize the window."""
|
|
247
|
+
glfw.maximize_window(self.window)
|
|
248
|
+
|
|
249
|
+
def minimize_window(self) -> None:
|
|
250
|
+
"""Minimize (iconify) the window."""
|
|
251
|
+
glfw.iconify_window(self.window)
|
|
252
|
+
|
|
253
|
+
def restore_window(self) -> None:
|
|
254
|
+
"""Restore the window from maximized or minimized state."""
|
|
255
|
+
glfw.restore_window(self.window)
|
|
256
|
+
|
|
173
257
|
def _on_resize(self, window: WindowType, width: int, height: int) -> None:
|
|
174
|
-
self.window_size
|
|
258
|
+
self.window_size.set(width, height)
|
|
175
259
|
fb_size = glfw.get_framebuffer_size(window)
|
|
176
260
|
self.ctx.viewport = (0, 0, fb_size[0], fb_size[1])
|
|
177
261
|
if hasattr(self, 'env') and hasattr(self.env, 'on_resize'):
|
|
@@ -181,35 +265,11 @@ class RootEnv:
|
|
|
181
265
|
self.env = env
|
|
182
266
|
return self
|
|
183
267
|
|
|
184
|
-
def init_rec(self, fps: int = 30, draw_on_screen: bool = True, path: str = 'output.mp4') ->
|
|
185
|
-
"""Initialize screen recording.
|
|
186
|
-
|
|
187
|
-
Args:
|
|
188
|
-
fps: Recording framerate (independent of application FPS)
|
|
189
|
-
draw_on_screen: Show recording stats overlay
|
|
190
|
-
path: Output video file path
|
|
191
|
-
|
|
192
|
-
Returns:
|
|
193
|
-
Self for method chaining
|
|
194
|
-
|
|
195
|
-
Controls:
|
|
196
|
-
F9: Pause/Resume recording
|
|
197
|
-
F10: Restart recording (clear buffer)
|
|
198
|
-
F12: Take screenshot
|
|
199
|
-
"""
|
|
268
|
+
def init_rec(self, fps: int = 30, draw_on_screen: bool = True, path: str = 'output.mp4') -> None:
|
|
200
269
|
from .winrec import WinRec
|
|
201
270
|
self.__winrecorder__ = WinRec(self, fps=fps, draw_on_screen=draw_on_screen, path=path)
|
|
202
|
-
return self
|
|
203
271
|
|
|
204
272
|
def load_shader_file(self, path: str) -> str:
|
|
205
|
-
"""Load shader source code from a file.
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
path: Path to shader file (relative to working directory or absolute)
|
|
209
|
-
|
|
210
|
-
Returns:
|
|
211
|
-
Shader source code as string
|
|
212
|
-
"""
|
|
213
273
|
if not os.path.exists(path):
|
|
214
274
|
raise FileNotFoundError(f"Shader file not found: {path}")
|
|
215
275
|
|
|
@@ -247,6 +307,10 @@ class RootEnv:
|
|
|
247
307
|
def __draw__(self) -> None:
|
|
248
308
|
self.ctx.clear(0.0, 0.0, 0.0, 1.0)
|
|
249
309
|
self.env.draw()
|
|
310
|
+
|
|
311
|
+
if self.draw_fps:
|
|
312
|
+
fps = 1.0 / self.delta if self.delta > 0 else 0.0
|
|
313
|
+
self.print(f"FPS: {fps:.2f}", V2(10, 10), scale=1.0, style=MONO_16_TEXT_STYLE, pivot=Pivots.TOP_LEFT)
|
|
250
314
|
|
|
251
315
|
# Screen recording: capture frame before overlay, draw stats after
|
|
252
316
|
if hasattr(self, '__winrecorder__'):
|
|
@@ -453,9 +517,9 @@ class RootEnv:
|
|
|
453
517
|
def print(
|
|
454
518
|
self,
|
|
455
519
|
text_or_label: str|TextLabel,
|
|
456
|
-
position:
|
|
520
|
+
position: Vector2D,
|
|
457
521
|
scale: float = 1.0,
|
|
458
|
-
style: TextStyle =
|
|
522
|
+
style: TextStyle = MONO_16_TEXT_STYLE,
|
|
459
523
|
pivot: Pivots|int = Pivots.TOP_LEFT,
|
|
460
524
|
save_cache: bool = False
|
|
461
525
|
) -> Optional[TextLabel]:
|
|
@@ -464,43 +528,89 @@ class RootEnv:
|
|
|
464
528
|
text_or_label.draw()
|
|
465
529
|
else:
|
|
466
530
|
if save_cache:
|
|
467
|
-
return self.text_renderer.create_label(str(text_or_label), position
|
|
531
|
+
return self.text_renderer.create_label(str(text_or_label), position.x, position.y, scale, style, pivot)
|
|
468
532
|
else:
|
|
469
|
-
self.text_renderer.draw_text(str(text_or_label), position, scale, style, pivot)
|
|
533
|
+
self.text_renderer.draw_text(str(text_or_label), (position.x, position.y), scale, style, pivot)
|
|
470
534
|
|
|
471
535
|
# ========== Shape Drawing Methods ==========
|
|
472
536
|
|
|
473
|
-
def draw_circle(self, center:
|
|
537
|
+
def draw_circle(self, center: Vector2D, radius: float,
|
|
538
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
539
|
+
rotation: float = 0.0,
|
|
540
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
541
|
+
border_width: float = 0.0,
|
|
542
|
+
antialiasing: float = 1.0) -> None:
|
|
474
543
|
"""Draw a circle. See ShapeRenderer.draw_circle for parameters."""
|
|
475
|
-
self.shape_renderer.draw_circle(center, radius,
|
|
476
|
-
|
|
477
|
-
|
|
544
|
+
self.shape_renderer.draw_circle(center, radius, color=color, rotation=rotation,
|
|
545
|
+
border_color=border_color, border_width=border_width,
|
|
546
|
+
antialiasing=antialiasing)
|
|
547
|
+
|
|
548
|
+
def draw_rect(self, position: Vector2D, size: Vector2D,
|
|
549
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
550
|
+
rotation: float = 0.0,
|
|
551
|
+
corner_radius: float = 0.0,
|
|
552
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
553
|
+
border_width: float = 0.0,
|
|
554
|
+
antialiasing: float = 1.0) -> None:
|
|
478
555
|
"""Draw a rectangle. See ShapeRenderer.draw_rect for parameters."""
|
|
479
|
-
self.shape_renderer.draw_rect(position, size,
|
|
480
|
-
|
|
481
|
-
|
|
556
|
+
self.shape_renderer.draw_rect(position, size, color=color, rotation=rotation,
|
|
557
|
+
corner_radius=corner_radius, border_color=border_color,
|
|
558
|
+
border_width=border_width, antialiasing=antialiasing)
|
|
559
|
+
|
|
560
|
+
def draw_line(self, start: Vector2D, end: Vector2D,
|
|
561
|
+
width: float = 1.0,
|
|
562
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
563
|
+
antialiasing: float = 1.0) -> None:
|
|
482
564
|
"""Draw a line. See ShapeRenderer.draw_line for parameters."""
|
|
483
|
-
self.shape_renderer.draw_line(start, end,
|
|
484
|
-
|
|
485
|
-
|
|
565
|
+
self.shape_renderer.draw_line((start.x, start.y), (end.x, end.y), width=width, color=color,
|
|
566
|
+
antialiasing=antialiasing)
|
|
567
|
+
|
|
568
|
+
def draw_lines(self, points,
|
|
569
|
+
width: float = 1.0,
|
|
570
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
571
|
+
antialiasing: float = 1.0,
|
|
572
|
+
closed: bool = False) -> None:
|
|
486
573
|
"""Draw a polyline. See ShapeRenderer.draw_lines for parameters."""
|
|
487
|
-
self.shape_renderer.draw_lines(points,
|
|
488
|
-
|
|
489
|
-
|
|
574
|
+
self.shape_renderer.draw_lines(points, width=width, color=color,
|
|
575
|
+
antialiasing=antialiasing, closed=closed)
|
|
576
|
+
|
|
577
|
+
def create_circle(self, center: Vector2D, radius: float,
|
|
578
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
579
|
+
rotation: float = 0.0,
|
|
580
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
581
|
+
border_width: float = 0.0,
|
|
582
|
+
antialiasing: float = 1.0) -> ShapeLabel:
|
|
490
583
|
"""Create a cached circle. See ShapeRenderer.create_circle for parameters."""
|
|
491
|
-
return self.shape_renderer.create_circle(center, radius,
|
|
492
|
-
|
|
493
|
-
|
|
584
|
+
return self.shape_renderer.create_circle(center, radius, color=color, rotation=rotation,
|
|
585
|
+
border_color=border_color, border_width=border_width,
|
|
586
|
+
antialiasing=antialiasing)
|
|
587
|
+
|
|
588
|
+
def create_rect(self, position: Vector2D, size: Vector2D,
|
|
589
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
590
|
+
rotation: float = 0.0,
|
|
591
|
+
corner_radius: float = 0.0,
|
|
592
|
+
border_color: ColorType = (0.0, 0.0, 0.0, 0.0),
|
|
593
|
+
border_width: float = 0.0,
|
|
594
|
+
antialiasing: float = 1.0) -> ShapeLabel:
|
|
494
595
|
"""Create a cached rectangle. See ShapeRenderer.create_rect for parameters."""
|
|
495
|
-
return self.shape_renderer.create_rect(position, size,
|
|
596
|
+
return self.shape_renderer.create_rect(position, size, color=color, rotation=rotation, corner_radius=corner_radius, border_color=border_color, border_width=border_width, antialiasing=antialiasing)
|
|
496
597
|
|
|
497
|
-
def create_line(self, start:
|
|
598
|
+
def create_line(self, start: Vector2D, end: Vector2D,
|
|
599
|
+
width: float = 1.0,
|
|
600
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
601
|
+
antialiasing: float = 1.0) -> ShapeLabel:
|
|
498
602
|
"""Create a cached line. See ShapeRenderer.create_line for parameters."""
|
|
499
|
-
return self.shape_renderer.create_line(start, end,
|
|
500
|
-
|
|
501
|
-
|
|
603
|
+
return self.shape_renderer.create_line(start, end, width=width, color=color,
|
|
604
|
+
antialiasing=antialiasing)
|
|
605
|
+
|
|
606
|
+
def create_lines(self, points,
|
|
607
|
+
width: float = 1.0,
|
|
608
|
+
color: ColorType = (1.0, 1.0, 1.0, 1.0),
|
|
609
|
+
antialiasing: float = 1.0,
|
|
610
|
+
closed: bool = False) -> ShapeLabel:
|
|
502
611
|
"""Create a cached polyline. See ShapeRenderer.create_lines for parameters."""
|
|
503
|
-
return self.shape_renderer.create_lines(points,
|
|
612
|
+
return self.shape_renderer.create_lines(points, width=width, color=color,
|
|
613
|
+
antialiasing=antialiasing, closed=closed)
|
|
504
614
|
|
|
505
615
|
def create_circle_batch(self, max_shapes: int = 10000) -> InstancedShapeBatch:
|
|
506
616
|
"""Create a batch for drawing multiple circles using GPU instancing."""
|
|
@@ -537,7 +647,7 @@ __all__ = [
|
|
|
537
647
|
'create_grid',
|
|
538
648
|
'create_circle',
|
|
539
649
|
# Type aliases
|
|
540
|
-
'
|
|
650
|
+
'Vector2D',
|
|
541
651
|
'ColorType',
|
|
542
652
|
# Color utilities
|
|
543
653
|
'Color',
|
|
@@ -567,7 +677,8 @@ __all__ = [
|
|
|
567
677
|
'TextLabel',
|
|
568
678
|
'TextStyle',
|
|
569
679
|
'Pivots',
|
|
570
|
-
'
|
|
680
|
+
'DEFAULT_16_TEXT_STYLE',
|
|
681
|
+
'MONO_16_TEXT_STYLE',
|
|
571
682
|
# Shape rendering
|
|
572
683
|
'ShapeRenderer',
|
|
573
684
|
'ShapeLabel',
|
|
@@ -583,6 +694,5 @@ __all__ = [
|
|
|
583
694
|
'set_pattr_value',
|
|
584
695
|
'get_uniform',
|
|
585
696
|
# Compilation flags
|
|
586
|
-
'_VECTOR_COMPILED',
|
|
587
697
|
'_COLOR_COMPILED',
|
|
588
698
|
]
|