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.
Binary file
e2D/cvectors.pyi ADDED
@@ -0,0 +1,243 @@
1
+ """
2
+ Type stubs for cvectors module (Cython-compiled Vector2D)
3
+ Provides type hints for all Vector2D operations
4
+ """
5
+
6
+ from typing import List, Tuple
7
+ from .types import Number
8
+ import numpy as np
9
+ import numpy.typing as npt
10
+
11
+ class Vector2D:
12
+ """
13
+ High-performance 2D vector class optimized for heavy simulations.
14
+
15
+ Uses contiguous numpy arrays and Cython for near-C performance.
16
+ All operations are optimized with no bounds checking and inline C math.
17
+ """
18
+
19
+ data: npt.NDArray[np.float64]
20
+
21
+ def __init__(self, x: float = 0.0, y: float = 0.0) -> None:
22
+ """Initialize vector with x, y components"""
23
+ ...
24
+
25
+ # Property accessors
26
+ @property
27
+ def x(self) -> float:
28
+ """X component"""
29
+ ...
30
+
31
+ @x.setter
32
+ def x(self, value: float) -> None: ...
33
+
34
+ @property
35
+ def y(self) -> float:
36
+ """Y component"""
37
+ ...
38
+
39
+ @y.setter
40
+ def y(self, value: float) -> None: ...
41
+
42
+ @property
43
+ def length(self) -> float:
44
+ """Vector length (magnitude)"""
45
+ ...
46
+
47
+ @property
48
+ def length_sqrd(self) -> float:
49
+ """Squared length (faster, avoids sqrt)"""
50
+ ...
51
+
52
+ @property
53
+ def angle(self) -> float:
54
+ """Angle of this vector"""
55
+ ...
56
+
57
+ @angle.setter
58
+ def angle(self, new_angle: float) -> None:
59
+ """Set angle while maintaining magnitude"""
60
+ ...
61
+
62
+ # Methods
63
+ def copy(self) -> Vector2D:
64
+ """Fast copy"""
65
+ ...
66
+
67
+ def set(self, x: float, y: float) -> None:
68
+ """Set both components"""
69
+ ...
70
+
71
+ def iadd(self, other: Vector2D) -> None:
72
+ """In-place addition"""
73
+ ...
74
+
75
+ def isub(self, other: Vector2D) -> None:
76
+ """In-place subtraction"""
77
+ ...
78
+
79
+ def imul(self, scalar: float) -> None:
80
+ """In-place scalar multiplication"""
81
+ ...
82
+
83
+ def idiv(self, scalar: float) -> None:
84
+ """In-place division"""
85
+ ...
86
+
87
+ def imul_vec(self, other: Vector2D) -> None:
88
+ """In-place component-wise multiplication"""
89
+ ...
90
+
91
+ def iadd_scalar(self, scalar: float) -> None:
92
+ """In-place scalar addition"""
93
+ ...
94
+
95
+ def isub_scalar(self, scalar: float) -> None:
96
+ """In-place scalar subtraction"""
97
+ ...
98
+
99
+ def normalize(self) -> None:
100
+ """Normalize in-place"""
101
+ ...
102
+
103
+ def clamp_inplace(self, min_val: Vector2D, max_val: Vector2D) -> None:
104
+ """Clamp components in-place"""
105
+ ...
106
+
107
+ def add(self, other: Vector2D) -> Vector2D:
108
+ """Addition (returns new vector)"""
109
+ ...
110
+
111
+ def sub(self, other: Vector2D) -> Vector2D:
112
+ """Subtraction (returns new vector)"""
113
+ ...
114
+
115
+ def mul(self, scalar: float) -> Vector2D:
116
+ """Scalar multiplication (returns new vector)"""
117
+ ...
118
+
119
+ def mul_vec(self, other: Vector2D) -> Vector2D:
120
+ """Component-wise multiplication (returns new vector)"""
121
+ ...
122
+
123
+ def normalized(self) -> Vector2D:
124
+ """Get normalized vector (returns new)"""
125
+ ...
126
+
127
+ def dot_product(self, other: Vector2D) -> float:
128
+ """Dot product"""
129
+ ...
130
+
131
+ def distance_to(self, other: Vector2D, rooted: bool = True) -> float:
132
+ """Distance to another vector"""
133
+ ...
134
+
135
+ def angle_to(self, other: Vector2D) -> float:
136
+ """Angle to another vector"""
137
+ ...
138
+
139
+ def rotate(self, angle: float) -> Vector2D:
140
+ """Rotate vector by angle (returns new)"""
141
+ ...
142
+
143
+ def irotate(self, angle: float) -> None:
144
+ """Rotate in-place"""
145
+ ...
146
+
147
+ def lerp(self, other: Vector2D, t: float) -> Vector2D:
148
+ """Linear interpolation"""
149
+ ...
150
+
151
+ def clamp(self, min_val: Vector2D, max_val: Vector2D) -> Vector2D:
152
+ """Clamp (returns new)"""
153
+ ...
154
+
155
+ def projection(self, other: Vector2D) -> Vector2D:
156
+ """Project this vector onto another"""
157
+ ...
158
+
159
+ def reflection(self, normal: Vector2D) -> Vector2D:
160
+ """Reflect vector across normal"""
161
+ ...
162
+
163
+ # Python operator overloads
164
+ def __add__(self, other: Vector2D | Number) -> Vector2D: ...
165
+ def __sub__(self, other: Vector2D | Number) -> Vector2D: ...
166
+ def __mul__(self, other: Vector2D | Number) -> Vector2D: ...
167
+ def __truediv__(self, other: Number) -> Vector2D: ...
168
+ def __iadd__(self, other: Vector2D | Number) -> Vector2D: ...
169
+ def __isub__(self, other: Vector2D | Number) -> Vector2D: ...
170
+ def __imul__(self, other: Vector2D | Number) -> Vector2D: ...
171
+ def __itruediv__(self, other: Number) -> Vector2D: ...
172
+ def __neg__(self) -> Vector2D: ...
173
+ def __abs__(self) -> Vector2D: ...
174
+ def __getitem__(self, idx: int) -> float: ...
175
+ def __setitem__(self, idx: int, value: float) -> None: ...
176
+ def __str__(self) -> str: ...
177
+ def __repr__(self) -> str: ...
178
+
179
+ # Utility methods
180
+ def to_list(self) -> List[float]:
181
+ """Convert to Python list"""
182
+ ...
183
+
184
+ def to_tuple(self) -> Tuple[float, float]:
185
+ """Convert to Python tuple"""
186
+ ...
187
+
188
+ # Class methods for common vectors
189
+ @staticmethod
190
+ def zero() -> Vector2D:
191
+ """Create zero vector (0, 0)"""
192
+ ...
193
+
194
+ @staticmethod
195
+ def one() -> Vector2D:
196
+ """Create one vector (1, 1)"""
197
+ ...
198
+
199
+ @staticmethod
200
+ def up() -> Vector2D:
201
+ """Create up vector (0, 1)"""
202
+ ...
203
+
204
+ @staticmethod
205
+ def down() -> Vector2D:
206
+ """Create down vector (0, -1)"""
207
+ ...
208
+
209
+ @staticmethod
210
+ def left() -> Vector2D:
211
+ """Create left vector (-1, 0)"""
212
+ ...
213
+
214
+ @staticmethod
215
+ def right() -> Vector2D:
216
+ """Create right vector (1, 0)"""
217
+ ...
218
+
219
+ @staticmethod
220
+ def random(min_val: float = 0.0, max_val: float = 1.0) -> Vector2D:
221
+ """Create random vector"""
222
+ ...
223
+
224
+ # Batch operations for processing many vectors at once
225
+ def batch_add_inplace(vectors: List[Vector2D], displacement: Vector2D) -> None:
226
+ """Add displacement to all vectors in-place (ultra-fast)"""
227
+ ...
228
+
229
+ def batch_scale_inplace(vectors: List[Vector2D], scalar: float) -> None:
230
+ """Scale all vectors in-place"""
231
+ ...
232
+
233
+ def batch_normalize_inplace(vectors: List[Vector2D]) -> None:
234
+ """Normalize all vectors in-place"""
235
+ ...
236
+
237
+ def vectors_to_array(vectors: List[Vector2D]) -> npt.NDArray[np.float64]:
238
+ """Convert list of vectors to numpy array (fast)"""
239
+ ...
240
+
241
+ def array_to_vectors(arr: npt.NDArray[np.float64]) -> List[Vector2D]:
242
+ """Convert numpy array to list of vectors (fast)"""
243
+ ...
e2D/devices.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from enum import Enum
2
2
  import glfw
3
+ from .types import VectorType, WindowType
3
4
 
4
5
  class KeyState(Enum):
5
6
  PRESSED = 1
@@ -7,12 +8,16 @@ class KeyState(Enum):
7
8
  JUST_RELEASED = 3
8
9
 
9
10
  class Keyboard:
11
+ pressed: set[int]
12
+ just_pressed: set[int]
13
+ just_released: set[int]
14
+
10
15
  def __init__(self) -> None:
11
16
  self.pressed = set()
12
17
  self.just_pressed = set()
13
18
  self.just_released = set()
14
19
 
15
- def _on_key(self, window, key, scancode, action, mods) -> None:
20
+ def _on_key(self, window: WindowType, key: int, scancode: int, action: int, mods: int) -> None:
16
21
  if action == glfw.PRESS:
17
22
  self.pressed.add(key)
18
23
  self.just_pressed.add(key)
@@ -24,7 +29,7 @@ class Keyboard:
24
29
  self.just_pressed.clear()
25
30
  self.just_released.clear()
26
31
 
27
- def get_key(self, key, state: KeyState) -> bool:
32
+ def get_key(self, key: int, state: KeyState|int = KeyState.PRESSED) -> bool:
28
33
  if state == KeyState.PRESSED:
29
34
  return key in self.pressed
30
35
  elif state == KeyState.JUST_PRESSED:
@@ -34,6 +39,14 @@ class Keyboard:
34
39
  return False
35
40
 
36
41
  class Mouse:
42
+ position: VectorType
43
+ last_position: VectorType
44
+ delta: VectorType
45
+ scroll: VectorType
46
+ pressed: set[int]
47
+ just_pressed: set[int]
48
+ just_released: set[int]
49
+
37
50
  def __init__(self) -> None:
38
51
  self.position = (0, 0)
39
52
  self.last_position = (0, 0)
@@ -43,10 +56,10 @@ class Mouse:
43
56
  self.just_pressed = set()
44
57
  self.just_released = set()
45
58
 
46
- def _on_cursor_pos(self, window, x, y) -> None:
59
+ def _on_cursor_pos(self, window: WindowType, x: float, y: float) -> None:
47
60
  self.position = (x, y)
48
61
 
49
- def _on_mouse_button(self, window, button, action, mods) -> None:
62
+ def _on_mouse_button(self, window: WindowType, button: int, action: int, mods: int) -> None:
50
63
  if action == glfw.PRESS:
51
64
  self.pressed.add(button)
52
65
  self.just_pressed.add(button)
@@ -54,7 +67,7 @@ class Mouse:
54
67
  self.pressed.discard(button)
55
68
  self.just_released.add(button)
56
69
 
57
- def _on_scroll(self, window, xoffset, yoffset) -> None:
70
+ def _on_scroll(self, window: WindowType, xoffset: float, yoffset: float) -> None:
58
71
  self.scroll = (xoffset, yoffset)
59
72
 
60
73
  def update(self) -> None:
@@ -64,7 +77,7 @@ class Mouse:
64
77
  self.just_released.clear()
65
78
  self.scroll = (0, 0)
66
79
 
67
- def get_button(self, button, state: KeyState) -> bool:
80
+ def get_button(self, button: int, state: KeyState) -> bool:
68
81
  if state == KeyState.PRESSED:
69
82
  return button in self.pressed
70
83
  elif state == KeyState.JUST_PRESSED:
e2D/devices.pyi ADDED
@@ -0,0 +1,65 @@
1
+ """
2
+ Type stubs for devices module
3
+ Input device handling (Keyboard and Mouse)
4
+ """
5
+
6
+ from enum import Enum
7
+ from .types import VectorType, WindowType
8
+
9
+ class KeyState(Enum):
10
+ """State of a keyboard key or mouse button"""
11
+ PRESSED: int
12
+ JUST_PRESSED: int
13
+ JUST_RELEASED: int
14
+
15
+ class Keyboard:
16
+ """Keyboard input handler"""
17
+ pressed: set[int]
18
+ just_pressed: set[int]
19
+ just_released: set[int]
20
+
21
+ def __init__(self) -> None: ...
22
+
23
+ def _on_key(self, window: WindowType, key: int, scancode: int, action: int, mods: int) -> None:
24
+ """Internal key event handler"""
25
+ ...
26
+
27
+ def update(self) -> None:
28
+ """Update keyboard state (call once per frame)"""
29
+ ...
30
+
31
+ def get_key(self, key: int, state: KeyState|int) -> bool:
32
+ """Check if a key is in the given state"""
33
+ ...
34
+
35
+ class Mouse:
36
+ """Mouse input handler"""
37
+ position: VectorType
38
+ last_position: VectorType
39
+ delta: VectorType
40
+ scroll: VectorType
41
+ pressed: set[int]
42
+ just_pressed: set[int]
43
+ just_released: set[int]
44
+
45
+ def __init__(self) -> None: ...
46
+
47
+ def _on_cursor_pos(self, window: WindowType, x: float, y: float) -> None:
48
+ """Internal cursor position event handler"""
49
+ ...
50
+
51
+ def _on_mouse_button(self, window: WindowType, button: int, action: int, mods: int) -> None:
52
+ """Internal mouse button event handler"""
53
+ ...
54
+
55
+ def _on_scroll(self, window: WindowType, xoffset: float, yoffset: float) -> None:
56
+ """Internal scroll event handler"""
57
+ ...
58
+
59
+ def update(self) -> None:
60
+ """Update mouse state (call once per frame)"""
61
+ ...
62
+
63
+ def get_button(self, button: int, state: KeyState | int) -> bool:
64
+ """Check if a mouse button is in the given state"""
65
+ ...
e2D/plots.py CHANGED
@@ -5,6 +5,10 @@ import struct
5
5
  from dataclasses import dataclass
6
6
  from enum import Enum
7
7
  import os
8
+ from .commons import set_uniform_block_binding
9
+ from .types import ColorType, ComputeShaderType, ComputeShaderType, Number, VAOType, VectorType, ContextType, ProgramType, BufferType, ArrayLike
10
+ from .colors import normalize_color
11
+ from .color_defs import GRAY10, GRAY50, WHITE, RED, CYAN
8
12
 
9
13
  class ShaderManager:
10
14
  """Cache and manage shader files for the plots module."""
@@ -25,14 +29,14 @@ class ShaderManager:
25
29
  return ShaderManager._cache[path]
26
30
 
27
31
  @staticmethod
28
- def create_program(ctx: moderngl.Context, vertex_path: str, fragment_path: str) -> moderngl.Program:
32
+ def create_program(ctx: ContextType, vertex_path: str, fragment_path: str) -> ProgramType:
29
33
  """Create a program from shader files."""
30
34
  vertex_shader = ShaderManager.load_shader(vertex_path)
31
35
  fragment_shader = ShaderManager.load_shader(fragment_path)
32
36
  return ctx.program(vertex_shader=vertex_shader, fragment_shader=fragment_shader)
33
37
 
34
38
  @staticmethod
35
- def create_compute(ctx: moderngl.Context, compute_path: str) -> moderngl.ComputeShader:
39
+ def create_compute(ctx: ContextType, compute_path: str) -> ComputeShaderType:
36
40
  """Create a compute shader from file."""
37
41
  compute_shader = ShaderManager.load_shader(compute_path)
38
42
  return ctx.compute_shader(compute_shader)
@@ -48,7 +52,15 @@ class View2D:
48
52
  float aspect; // 24
49
53
  float _pad; // 28
50
54
  """
51
- def __init__(self, ctx: moderngl.Context, binding: int = 0) -> None:
55
+ ctx: ContextType
56
+ binding: int
57
+ center: ArrayLike
58
+ zoom: float
59
+ aspect: float
60
+ resolution: ArrayLike
61
+ buffer: BufferType
62
+
63
+ def __init__(self, ctx: ContextType, binding: int = 0) -> None:
52
64
  self.ctx = ctx
53
65
  self.binding = binding
54
66
  self.center = np.array([0.0, 0.0], dtype='f4')
@@ -60,7 +72,7 @@ class View2D:
60
72
  self.buffer.bind_to_uniform_block(self.binding)
61
73
  self.update_buffer()
62
74
 
63
- def update_win_size(self, width: int, height: int) -> None:
75
+ def update_win_size(self, width: float, height: float) -> None:
64
76
  self.resolution = np.array([width, height], dtype='f4')
65
77
  self.aspect = width / height if height > 0 else 1.0
66
78
  self.update_buffer()
@@ -99,23 +111,23 @@ class View2D:
99
111
 
100
112
  @dataclass
101
113
  class PlotSettings:
102
- bg_color: tuple = (0.1, 0.1, 0.1, 1.0)
114
+ bg_color: ColorType = GRAY10
103
115
  show_axis: bool = True
104
- axis_color: tuple = (0.5, 0.5, 0.5, 1.0)
116
+ axis_color: ColorType = GRAY50
105
117
  axis_width: float = 2.0
106
118
  show_grid: bool = True
107
- grid_color: tuple = (0.2, 0.2, 0.2, 1.0)
119
+ grid_color: ColorType = (0.2, 0.2, 0.2, 1.0)
108
120
  grid_spacing: float = 1.0
109
121
 
110
122
  @dataclass
111
123
  class CurveSettings:
112
- color: tuple = (1.0, 1.0, 1.0, 1.0)
124
+ color: ColorType = WHITE
113
125
  width: float = 2.0
114
126
  count: int = 1024
115
127
 
116
128
  @dataclass
117
129
  class ImplicitSettings:
118
- color: tuple = (0.4, 0.6, 1.0, 1.0)
130
+ color: ColorType = CYAN
119
131
  thickness: float = 2.0
120
132
 
121
133
  class LineType(Enum):
@@ -127,18 +139,32 @@ class LineType(Enum):
127
139
 
128
140
  @dataclass
129
141
  class StreamSettings:
130
- point_color: tuple = (1.0, 0.0, 0.0, 1.0)
142
+ point_color: ColorType = RED
131
143
  point_radius: float = 5.0
132
144
  show_points: bool = True
133
145
  round_points: bool = True
134
146
  line_type: LineType = LineType.DIRECT
135
- line_color: tuple = (1.0, 0.0, 0.0, 1.0)
147
+ line_color: ColorType = RED
136
148
  line_width: float = 2.0
137
149
  curve_segments: int = 10
138
150
 
139
151
  class Plot2D:
140
152
  """A specific rectangular area on the screen for plotting."""
141
- def __init__(self, ctx: moderngl.Context, top_left: tuple[int, int], bottom_right: tuple[int, int], settings: Optional[PlotSettings] = None) -> None:
153
+ ctx: ContextType
154
+ top_left: VectorType
155
+ bottom_right: VectorType
156
+ settings: PlotSettings
157
+ width: Number
158
+ height: Number
159
+ view: View2D
160
+ viewport: tuple[int, int, int, int]
161
+ grid_prog: ProgramType
162
+ grid_quad: BufferType
163
+ grid_vao: VAOType
164
+ is_dragging: bool
165
+ last_mouse_pos: VectorType
166
+
167
+ def __init__(self, ctx: ContextType, top_left: VectorType, bottom_right: VectorType, settings: Optional[PlotSettings] = None) -> None:
142
168
  self.ctx = ctx
143
169
  self.top_left = top_left
144
170
  self.bottom_right = bottom_right
@@ -150,7 +176,7 @@ class Plot2D:
150
176
  self.view = View2D(ctx)
151
177
  self.view.update_win_size(self.width, self.height)
152
178
 
153
- self.viewport = (top_left[0], 1080 - bottom_right[1], self.width, self.height)
179
+ self.viewport = (int(top_left[0]), 1080 - int(bottom_right[1]), int(self.width), int(self.height))
154
180
  self._init_grid_renderer()
155
181
 
156
182
  self.is_dragging = False
@@ -163,27 +189,27 @@ class Plot2D:
163
189
  "shaders/plot_grid_fragment.glsl"
164
190
  )
165
191
  try:
166
- self.grid_prog['View'].binding = 0 # type: ignore
192
+ set_uniform_block_binding(self.grid_prog, 'View', 0)
167
193
  except:
168
194
  pass
169
195
  self.grid_quad = self.ctx.buffer(np.array([-1,-1, 1,-1, -1,1, 1,1], dtype='f4'))
170
196
  self.grid_vao = self.ctx.simple_vertex_array(self.grid_prog, self.grid_quad, "in_vert")
171
197
 
172
- def set_rect(self, top_left: tuple[int, int], bottom_right: tuple[int, int]):
198
+ def set_rect(self, top_left: VectorType, bottom_right: VectorType) -> None:
173
199
  self.top_left = top_left
174
200
  self.bottom_right = bottom_right
175
201
  self.width = bottom_right[0] - top_left[0]
176
202
  self.height = bottom_right[1] - top_left[1]
177
203
  self.view.update_win_size(self.width, self.height)
178
204
 
179
- def update_window_size(self, win_width: int, win_height: int):
205
+ def update_window_size(self, win_width: int, win_height: int) -> None:
180
206
  x = self.top_left[0]
181
207
  w = self.width
182
208
  h = self.height
183
- y = win_height - self.bottom_right[1]
184
- self.viewport = (x, y, w, h)
209
+ y = win_height - self.bottom_right[1]
210
+ self.viewport = (int(x), int(y), int(w), int(h))
185
211
 
186
- def render(self, draw_callback):
212
+ def render(self, draw_callback) -> None:
187
213
  self.ctx.viewport = self.viewport
188
214
  self.ctx.scissor = self.viewport
189
215
  self.ctx.clear(*self.settings.bg_color)
@@ -220,7 +246,7 @@ class Plot2D:
220
246
 
221
247
  class GpuStream:
222
248
  """Ring-buffer on GPU for high-performance point streaming."""
223
- def __init__(self, ctx: moderngl.Context, capacity: int = 100000, settings: Optional[StreamSettings] = None) -> None:
249
+ def __init__(self, ctx: ContextType, capacity: int = 100000, settings: Optional[StreamSettings] = None) -> None:
224
250
  self.ctx = ctx
225
251
  self.capacity = capacity
226
252
  self.settings = settings if settings else StreamSettings()
@@ -237,7 +263,7 @@ class GpuStream:
237
263
  "shaders/stream_fragment.glsl"
238
264
  )
239
265
  try:
240
- self.prog['View'].binding = 0 # type: ignore
266
+ set_uniform_block_binding(self.prog, 'View', 0)
241
267
  except:
242
268
  pass
243
269
  self.vao = ctx.vertex_array(self.prog, [])
@@ -311,7 +337,7 @@ class GpuStream:
311
337
  """
312
338
  )
313
339
  try:
314
- self.smooth_prog['View'].binding = 0 # type: ignore
340
+ set_uniform_block_binding(self.smooth_prog, 'View', 0)
315
341
  except:
316
342
  pass
317
343
  self.smooth_vao = ctx.vertex_array(self.smooth_prog, [])
@@ -374,7 +400,7 @@ class GpuStream:
374
400
  self.prog['round_points'] = self.settings.round_points
375
401
  self.vao.render(moderngl.POINTS, vertices=self.size)
376
402
 
377
- def shift_points(self, offset: tuple[float, float]) -> None:
403
+ def shift_points(self, offset: VectorType) -> None:
378
404
  """Shifts all existing points by the given offset using a Compute Shader."""
379
405
  if not hasattr(self, 'shift_prog'):
380
406
  self.shift_prog = ShaderManager.create_compute(
@@ -392,7 +418,7 @@ class GpuStream:
392
418
 
393
419
  class ComputeCurve:
394
420
  """Parametric curve p(t) evaluated entirely on GPU."""
395
- def __init__(self, ctx: moderngl.Context, func_body: str, t_range: tuple, count: int = 1024, settings: Optional[CurveSettings] = None):
421
+ def __init__(self, ctx: ContextType, func_body: str, t_range: tuple, count: int = 1024, settings: Optional[CurveSettings] = None):
396
422
  self.ctx = ctx
397
423
  self.count = count
398
424
  self.t_range = t_range
@@ -432,7 +458,7 @@ class ComputeCurve:
432
458
  "shaders/curve_fragment.glsl"
433
459
  )
434
460
  try:
435
- self.render_prog['View'].binding = 0 # type: ignore
461
+ set_uniform_block_binding(self.render_prog, 'View', 0)
436
462
  except:
437
463
  pass
438
464
  self.vao = ctx.simple_vertex_array(self.render_prog, self.vbo, "in_pos")
@@ -454,7 +480,7 @@ class ComputeCurve:
454
480
 
455
481
  class ImplicitPlot:
456
482
  """Rendering of f(x,y)=0 via Fragment Shader and SDF."""
457
- def __init__(self, ctx: moderngl.Context, func_body: str, settings: Optional[ImplicitSettings] = None):
483
+ def __init__(self, ctx: ContextType, func_body: str, settings: Optional[ImplicitSettings] = None):
458
484
  self.ctx = ctx
459
485
  self.settings = settings if settings else ImplicitSettings()
460
486
 
@@ -506,7 +532,7 @@ class ImplicitPlot:
506
532
 
507
533
  self.prog = ctx.program(vertex_shader=vs_src, fragment_shader=fs_src)
508
534
  try:
509
- self.prog['View'].binding = 0 # type: ignore
535
+ set_uniform_block_binding(self.prog, 'View', 0)
510
536
  except:
511
537
  pass
512
538
  self.vao = ctx.simple_vertex_array(self.prog, self.quad, "in_vert")
@@ -518,7 +544,7 @@ class ImplicitPlot:
518
544
 
519
545
  class SegmentDisplay:
520
546
  """Simple 7-segment display renderer for numbers."""
521
- def __init__(self, ctx: moderngl.Context):
547
+ def __init__(self, ctx: ContextType):
522
548
  self.ctx = ctx
523
549
  self.prog = ShaderManager.create_program(
524
550
  ctx,
@@ -543,7 +569,7 @@ class SegmentDisplay:
543
569
  '.': [7]
544
570
  }
545
571
 
546
- def draw_number(self, text: str, x: float, y: float, size: float = 20.0, color: tuple = (1.0, 1.0, 1.0, 1.0)):
572
+ def draw_number(self, text: str, x: float, y: float, size: float = 20.0, color: ColorType = WHITE):
547
573
  vertices = []
548
574
  cursor_x = x
549
575
  w = size * 0.5