e2D 1.4.19__py3-none-any.whl → 1.4.23__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.
e2D/colors.py CHANGED
@@ -1,7 +1,8 @@
1
+ from __future__ import annotations
1
2
  from colorsys import hsv_to_rgb as __hsv_to_rgb_def__, hls_to_rgb as __hls_to_rgb_def__, rgb_to_hls as __rgb_to_hls__, rgb_to_hsv as __rgb_to_hsv__
2
3
  from typing import Any, Callable, Generator, Literal
3
4
  from pygame.color import Color as __color_pygame__
4
- from random import randint as __randint__
5
+ from random import random as __rand__
5
6
 
6
7
  RGB_COLOR_MODE = "rgb"
7
8
  RGBA_COLOR_MODE = "rgba"
@@ -15,11 +16,11 @@ HLS_COLOR_MODE = "hls"
15
16
 
16
17
  __LITERAL_COLOR_MODES__ = Literal["rgb","rgba","bgr","bgra","g","hsv","hls"] #,"cmyk","lab"]
17
18
 
18
- def __hsv_to_rgb__(h:int|float, s:int|float, v:int|float) -> tuple[int|float, int|float, int|float]:
19
+ def __hsv_to_rgb__(h:"int|float", s:"int|float", v:"int|float") -> tuple["int|float", "int|float", "int|float"]:
19
20
  r,g,b = __hsv_to_rgb_def__(h, s, v)
20
21
  return r*255, g*255, b*255
21
22
 
22
- def __hls_to_rgb__(h:int|float, s:int|float, v:int|float) -> tuple[int|float, int|float, int|float]:
23
+ def __hls_to_rgb__(h:"int|float", s:"int|float", v:"int|float") -> tuple["int|float", "int|float", "int|float"]:
23
24
  r,g,b = __hls_to_rgb_def__(h, s, v)
24
25
  return r*255, g*255, b*255
25
26
 
@@ -82,6 +83,12 @@ __conversion_table__ :dict[__LITERAL_COLOR_MODES__, dict[__LITERAL_COLOR_MODES__
82
83
  },
83
84
  }
84
85
 
86
+ def pygamize_color(color: "__color_pygame__|Color") -> "__color_pygame__":
87
+ return color() if isinstance(color, Color) else color
88
+
89
+ def unpygamize_color(color: "__color_pygame__|Color") -> "Color":
90
+ return Color(*color[:], mode=RGBA_COLOR_MODE) if isinstance(color, __color_pygame__) else color
91
+
85
92
  class Color:
86
93
  def __init__(self, *values, mode:__LITERAL_COLOR_MODES__=RGB_COLOR_MODE) -> None:
87
94
  self.__dict__ = dict(zip(mode, values))
@@ -104,35 +111,35 @@ class Color:
104
111
  return (d ** .5) if rooted else d
105
112
 
106
113
  @classmethod
107
- def new_rgb(cls, r:int|float, g:int|float, b:int|float) -> "Color":
114
+ def new_rgb(cls, r:"int|float", g:"int|float", b:"int|float") -> "Color":
108
115
  return Color(r,g,b, mode=RGB_COLOR_MODE)
109
116
  @classmethod
110
- def new_rgba(cls, r:int|float, g:int|float, b:int|float, a:int|float) -> "Color":
117
+ def new_rgba(cls, r:"int|float", g:"int|float", b:"int|float", a:"int|float") -> "Color":
111
118
  return Color(r,g,b,a, mode=RGBA_COLOR_MODE)
112
119
  @classmethod
113
- def new_bgr(cls, b:int|float, g:int|float, r:int|float) -> "Color":
120
+ def new_bgr(cls, b:"int|float", g:"int|float", r:"int|float") -> "Color":
114
121
  return Color(b,g,r, mode=BGR_COLOR_MODE)
115
122
  @classmethod
116
- def new_bgra(cls, b:int|float, g:int|float, r:int|float, a:int|float) -> "Color":
123
+ def new_bgra(cls, b:"int|float", g:"int|float", r:"int|float", a:"int|float") -> "Color":
117
124
  return Color(b,g,r,a, mode=BGRA_COLOR_MODE)
118
125
  @classmethod
119
126
  def new_g(cls, g) -> "Color":
120
127
  return Color(g, mode=GRAY_COLOR_MODE)
121
128
  @classmethod
122
- def new_hsv(cls, h:int|float, s:int|float, v:int|float) -> "Color":
129
+ def new_hsv(cls, h:"int|float", s:"int|float", v:"int|float") -> "Color":
123
130
  return Color(h,s,v, mode=HSV_COLOR_MODE)
124
131
  @classmethod
125
- def new_hls(cls, h:int|float, l:int|float, s:int|float) -> "Color":
132
+ def new_hls(cls, h:"int|float", l:"int|float", s:"int|float") -> "Color":
126
133
  return Color(h,l,s, mode=HLS_COLOR_MODE)
127
134
  # @classmethod
128
- # def new_cmyk(cls, c:int|float, m:int|float, y:int|float, k:int|float) -> Color:
135
+ # def new_cmyk(cls, c:"int|float", m:"int|float", y:"int|float", k:"int|float") -> Color:
129
136
  # return Color(c,m,y,k, mode=CMYK_COLOR_MODE)
130
137
  # @classmethod
131
- # def new_lab(cls, l:int|float, a:int|float, b:int|float) -> Color:
138
+ # def new_lab(cls, l:"int|float", a:"int|float", b:"int|float") -> Color:
132
139
  # return Color(l,a,b, mode=LAB_COLOR_MODE)
133
140
 
134
141
  @property
135
- def values(self) -> tuple[int|float, ...]:
142
+ def values(self) -> tuple["int|float", ...]:
136
143
  return tuple(self.__dict__.values())[:-1]
137
144
 
138
145
  @property
@@ -140,7 +147,7 @@ class Color:
140
147
  return tuple(self.__dict__.keys())[:-1]
141
148
 
142
149
  @property
143
- def items(self) -> tuple[tuple[str, int|float], ...]:
150
+ def items(self) -> tuple[tuple[str, "int|float"], ...]:
144
151
  return tuple(self.__dict__.items())[:-1]
145
152
 
146
153
  def copy(self) -> "Color":
@@ -179,7 +186,7 @@ class Color:
179
186
  return "Color(" + ", ".join(f"{k}:{v}" for k, v in self.items) + ")"
180
187
 
181
188
  def __call__(self) -> __color_pygame__:
182
- return __color_pygame__(int(self.r), int(self.g), int(self.b))
189
+ return __color_pygame__(int(self.r), int(self.g), int(self.b)) if self.mode == RGB_COLOR_MODE else __color_pygame__(int(self.r), int(self.g), int(self.b), int(self.a))
183
190
 
184
191
  # fast operations Vector2D.operation(both,x,y)
185
192
  def add(self, all3=.0, r=.0, g=.0, b=.0) -> "Color":
@@ -386,7 +393,7 @@ class Color:
386
393
  def __float__(self) -> "Color":
387
394
  return Color(float(self.r), float(self.g), float(self.b))
388
395
 
389
- def __getitem__(self, n) -> int|float:
396
+ def __getitem__(self, n) -> "int|float":
390
397
  return self.values[n] if isinstance(n, int) else self.values[self.keys.index(n)]
391
398
 
392
399
  def __iter__(self) -> Generator[float, Any, None]:
@@ -406,8 +413,10 @@ class Color:
406
413
  try:
407
414
  return cls(*other.values, mode=other.mode)
408
415
  except:
409
- raise TypeError(f"The value {other} of type {type(other)} is not a num type: [{int|float}] nor an array type: [{list|tuple}]")
416
+ raise TypeError(f"The value {other} of type {type(other)} is not a num type: [int|float] nor an array type: [list|tuple]")
410
417
 
418
+ @classmethod
419
+ def transparent(cls) -> "Color": return Color(0,0,0,0, mode=RGBA_COLOR_MODE)
411
420
  @classmethod
412
421
  def white(cls) -> "Color": return Color(255,255,255)
413
422
  @classmethod
@@ -418,23 +427,44 @@ class Color:
418
427
  def green(cls) -> "Color": return Color(0,255,0)
419
428
  @classmethod
420
429
  def blue(cls) -> "Color": return Color(0,0,255)
421
-
422
- # @classmethod
423
- # def (cls) -> "Color": return Color(0,0,255)
424
-
425
430
  @classmethod
426
- def randomize(cls) -> "Color":
427
- return Color(__randint__(0,255), __randint__(0,255), __randint__(0,255))
428
-
431
+ def cyan(cls) -> "Color": return Color(0,255,255)
432
+ @classmethod
433
+ def magenta(cls) -> "Color": return Color(255,0,255)
434
+ @classmethod
435
+ def yellow(cls) -> "Color": return Color(255,255,0)
429
436
 
437
+ @classmethod
438
+ def randomize(cls, start=0, end=255, func=lambda val:val) -> "Color":
439
+ if not isinstance(start, Color):
440
+ if isinstance(start, (int, float)):
441
+ start = Color(start, start, start)
442
+ else:
443
+ raise Exception(f"\nArg start must be in [Color, int, float, tuple, list] not a [{type(start)}]\n")
444
+ if not isinstance(end, Color):
445
+ if isinstance(end, (int, float)):
446
+ end = Color(end, end, end)
447
+ else:
448
+ raise Exception(f"\nArg end must be in [Color, int, float, tuple, list] not a [{type(end)}]\n")
449
+ return start + Color(func(__rand__()), func(__rand__()), func(__rand__())) * (end - start)
450
+
451
+
452
+ TRANSPARENT_COLOR = Color.transparent()
430
453
  WHITE_COLOR = Color.white()
431
454
  BLACK_COLOR = Color.black()
432
455
  RED_COLOR = Color.red()
433
456
  GREEN_COLOR = Color.green()
434
457
  BLUE_COLOR = Color.blue()
458
+ CYAN_COLOR = Color.cyan()
459
+ MAGENTA_COLOR = Color.magenta()
460
+ YELLOW_COLOR = Color.yellow()
435
461
 
462
+ TRANSPARENT_COLOR_PYG = TRANSPARENT_COLOR()
436
463
  WHITE_COLOR_PYG = WHITE_COLOR()
437
464
  BLACK_COLOR_PYG = BLACK_COLOR()
438
465
  RED_COLOR_PYG = RED_COLOR()
439
466
  GREEN_COLOR_PYG = GREEN_COLOR()
440
467
  BLUE_COLOR_PYG = BLUE_COLOR()
468
+ CYAN_COLOR_PYG = CYAN_COLOR()
469
+ MAGENTA_COLOR_PYG = MAGENTA_COLOR()
470
+ YELLOW_COLOR_PYG = YELLOW_COLOR()
e2D/envs.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Literal
2
+ from typing import Literal, Optional
3
3
 
4
4
  from .utils import *
5
5
  import pygame as pg
@@ -42,13 +42,15 @@ class RootEnv:
42
42
  quit_on_key_pressed : None|int = pg.K_x,
43
43
  vsync : bool = True,
44
44
  window_flags : int = pg.DOUBLEBUF,
45
+ display_index : int = 0,
45
46
  clear_screen_each_frame : bool = True) -> None:
46
47
  self.quit = False
47
48
  self.__screen_size__ :Vector2D= screen_size
48
49
 
49
50
  self.__vsync__ = vsync
50
51
  self.__flags__ = window_flags
51
- self.screen = pg.display.set_mode(self.__screen_size__(), vsync=self.__vsync__, flags=self.__flags__)
52
+ self.__display_index__ = display_index
53
+ self.screen = pg.display.set_mode(self.__screen_size__(), vsync=self.__vsync__, flags=self.__flags__, display=self.__display_index__)
52
54
 
53
55
  self.clock = pg.time.Clock()
54
56
  self.keyboard = Keyboard()
@@ -56,15 +58,38 @@ class RootEnv:
56
58
 
57
59
  self.target_fps = target_fps
58
60
  self.current_fps = self.target_fps if self.target_fps != 0 else 1
61
+ self.__dt__ = 1 / self.current_fps
59
62
  self.current_frame = 0
60
63
  self.show_fps = show_fps
61
64
  self.events :list[pg.event.Event]= []
62
- self.background_color = BLACK_COLOR_PYG
65
+
66
+ # Track start time for accurate runtime calculation (excluding compilation time)
67
+ self.set_starting_timer()
68
+
69
+ self.__background_color__ :pg.Color= BLACK_COLOR_PYG
70
+
63
71
  self.clear_screen_each_frame = clear_screen_each_frame
64
72
  self.utils :dict[int|str, Util]= {}
65
73
  self.selected_util :Util|None = None
66
74
  self.__quit_on_key_pressed__ = quit_on_key_pressed
67
75
 
76
+ self.fps_label = Label(str(round(self.current_fps,2)), self.screen_size * .01, V2(250, 50), BLACK_COLOR_PYG, TRANSPARENT_COLOR_PYG, WHITE_COLOR_PYG, border_width=0, starting_hidden=(not self.show_fps), pivot_position="top_left", font=FONT_ARIAL_32)
77
+ self.add_utils(self.fps_label)
78
+
79
+ def set_starting_timer(self) -> None:
80
+ self.__start_time__ = pg.time.get_ticks()
81
+
82
+ def init_rec(self, fps:int=30, draw_on_screen:bool=True, path:str='output.mp4', font:pg.font.Font=FONT_MONOSPACE_16) -> None:
83
+ from .winrec import WinRec
84
+ self.__winrecorder__ = WinRec(self, fps=fps, draw_on_screen=draw_on_screen, path=path, font=font)
85
+
86
+ @property
87
+ def background_color(self) -> Color:
88
+ return unpygamize_color(self.__background_color__)
89
+ @background_color.setter
90
+ def background_color(self, color: Color|pg.Color) -> None:
91
+ self.__background_color__ = pygamize_color(color)
92
+
68
93
  @property
69
94
  def screen_size(self) -> Vector2D:
70
95
  return self.__screen_size__
@@ -72,97 +97,127 @@ class RootEnv:
72
97
  @screen_size.setter
73
98
  def screen_size(self, new_size:Vector2D) -> None:
74
99
  self.__screen_size__ = new_size
75
- self.screen = pg.display.set_mode(self.__screen_size__(), vsync=self.__vsync__, flags=self.__flags__)
100
+ self.screen = pg.display.set_mode(self.__screen_size__(), vsync=self.__vsync__, flags=self.__flags__, display=self.__display_index__)
101
+
102
+ def update_screen_to_new_size(self) -> None:
103
+ self.screen = pg.display.set_mode(self.__screen_size__(), vsync=self.__vsync__, flags=self.__flags__, display=self.__display_index__)
76
104
 
77
105
  @property
78
- def delta(self) -> int:
79
- return self.clock.get_time() / 1000
106
+ def delta(self) -> float:
107
+ return self.__dt__
80
108
 
81
109
  def get_teoric_max_fps(self) -> float:
82
110
  rawdelta = self.clock.get_rawtime()
83
111
  return (1000 / rawdelta) if rawdelta != 0 else 1
84
112
 
85
- def update_screen_mode(self, vsync:None|bool=None, flags=None) -> None:
86
- self.__vsync__ = vsync
87
- self.__flags__ = flags
113
+ def update_screen_mode(self, vsync:Optional[bool]=None, flags:Optional[int]=None, display_index:Optional[int]=None) -> None:
114
+ if vsync is not None: self.__vsync__ = vsync
115
+ if flags is not None: self.__flags__ = flags
116
+ if display_index is not None: self.__display_index__ = display_index
117
+ self.screen = pg.display.set_mode(self.__screen_size__(), vsync=self.__vsync__, flags=self.__flags__, display=self.__display_index__)
88
118
 
89
119
  def sleep(self, seconds:int|float, precise_delay=False) -> None:
90
120
  if precise_delay:
91
- pg.time.delay(seconds * 1000)
121
+ pg.time.delay(int(seconds * 1000))
92
122
  else:
93
- pg.time.wait(seconds * 1000)
123
+ pg.time.wait(int(seconds * 1000))
94
124
 
95
125
  def add_utils(self, *utils:Util) -> None:
96
126
  for util in utils:
97
127
  if util.surface == None: util.surface = self.screen
98
128
  util.rootEnv = self
129
+ util.id = self.__new_util_id__()
130
+ util.__render__()
99
131
  self.utils[util.id] = util
100
132
 
101
133
  def remove_utils(self, *utils:int|str|Util) -> None:
102
134
  for uid in utils:
103
- if uid in self.utils:
104
- del self.utils[uid]
105
- elif isinstance(uid, Util):
135
+ if isinstance(uid, Util):
106
136
  del self.utils[uid.id]
137
+ elif uid in self.utils:
138
+ del self.utils[uid]
107
139
  else:
108
140
  raise Exception(f"Unknown util type: {uid}")
141
+
142
+ def __new_util_id__(self) -> int:
143
+ if not self.utils: return 0
144
+ else: return len(self.utils.keys()) + 1
145
+
146
+ def get_util(self, uid:int|str) -> Util|None:
147
+ if isinstance(uid, Util):
148
+ return self.utils.get(uid.id)
149
+ elif isinstance(uid, int) or isinstance(uid, str):
150
+ return self.utils.get(uid)
151
+ else:
152
+ raise Exception(f"Unknown util type: {uid}")
109
153
 
110
154
  @property
111
155
  def runtime_seconds(self) -> float:
112
- return pg.time.get_ticks() / 1e3
156
+ return (pg.time.get_ticks() - self.__start_time__) / 1e3
113
157
 
114
158
  def init(self, sub_env:DefEnv) -> None:
115
159
  self.env = sub_env
116
160
 
117
161
  def clear(self) -> None:
118
- self.screen.fill(self.background_color)
162
+ self.screen.fill(self.__background_color__)
119
163
 
120
164
  def clear_rect(self, position:Vector2D, size:Vector2D) -> None:
121
- self.screen.fill(self.background_color, position() + size())
165
+ self.screen.fill(self.__background_color__, position() + size())
122
166
 
123
167
  def print(self,
124
- text : str,
125
- position : Vector2D,
126
- color : pg.color.Color = WHITE_COLOR_PYG,
127
- pivot_position : __LITERAL_PIVOT_POSITIONS__ = "top_left",
128
- font : pg.font.Font = FONT_ARIAL_32,
129
- bg_color : None|pg.color.Color = None,
130
- border_color : pg.color.Color = WHITE_COLOR_PYG,
131
- border_width : float = 0.0,
132
- border_radius : int|list[int]|tuple[int,int,int,int] = -1,
133
- margin : Vector2D = Vector2D.zero(),
134
- personalized_surface : pg.Surface|None = None
135
- ) -> None:
168
+ text : str,
169
+ position : Vector2D,
170
+ color : pg.color.Color = WHITE_COLOR_PYG,
171
+ pivot_position : __LITERAL_PIVOT_POSITIONS__ = "top_left",
172
+ font : pg.font.Font = FONT_ARIAL_32,
173
+ bg_color : None|pg.color.Color = None,
174
+ border_color : pg.color.Color = WHITE_COLOR_PYG,
175
+ border_width : float = 0.0,
176
+ border_radius : int|list[int]|tuple[int,int,int,int] = -1,
177
+ margin : Vector2D = Vector2D.zero(),
178
+ personalized_surface : pg.Surface|None = None
179
+ ) -> Vector2D:
180
+
136
181
  text_box = font.render(text, True, color)
137
182
  size = Vector2D(*text_box.get_size()) + margin * 2
138
183
  pivotted_position = position - size * __PIVOT_POSITIONS_MULTIPLIER__[pivot_position] + margin
139
- if not any(isinstance(border_radius, cls) for cls in {tuple, list}): border_radius = [border_radius]*4
184
+ if not any(isinstance(border_radius, cls) for cls in {tuple, list}): border_radius = [border_radius] * 4
140
185
  surface = (self.screen if personalized_surface == None else personalized_surface)
141
186
  if bg_color != None:
142
187
  pg.draw.rect(surface, bg_color, (pivotted_position - margin)() + size(), 0, -1, *border_radius)
143
188
  if border_width:
144
189
  pg.draw.rect(surface, border_color, (pivotted_position - margin)() + size(), border_width, -1, *border_radius)
145
190
  surface.blit(text_box, pivotted_position())
191
+ return size
146
192
 
147
193
  def __draw__(self) -> None:
148
- self.clock.tick(self.target_fps)
194
+ self.__dt__ = self.clock.tick(self.target_fps) / 1000.0
149
195
  self.current_fps = self.clock.get_fps()
196
+ self.fps_label.text = str(round(self.current_fps, 2))
197
+
150
198
  if self.clear_screen_each_frame: self.clear()
151
199
 
152
200
  self.env.draw()
153
- for util in self.utils.values(): util.draw()
201
+ for util in self.utils.values(): util.__draw__()
202
+
203
+ # Save frame first (clean), then draw info overlay (not saved)
204
+ if hasattr(self, "__winrecorder__"):
205
+ self.__winrecorder__.update() # Captures frame without stats
206
+ self.__winrecorder__.draw() # Draws stats on screen only
154
207
 
155
- if self.show_fps: self.print(str(round(self.current_fps,2)), self.screen_size * .01, bg_color=BLACK_COLOR_PYG)
156
208
  pg.display.flip()
157
209
 
158
210
  def __update__(self) -> None:
159
211
  self.mouse.update()
160
212
  self.keyboard.update()
161
213
  self.env.update()
162
- for util in self.utils.values(): util.update()
214
+ for util in self.utils.values(): util.__update__()
163
215
 
164
216
  def frame(self) -> None:
165
- self.events = pg.event.get()
217
+ try:
218
+ self.events = pg.event.get()
219
+ except SystemError:
220
+ raise Warning(f"Pygame error with event drivers. Try restarting the program. If the error persists, try restarting your computer.")
166
221
  self.current_frame += 1
167
222
  self.__update__()
168
223
  self.__draw__()
@@ -170,4 +225,5 @@ class RootEnv:
170
225
  for event in self.events:
171
226
  if event.type == pg.QUIT or ((event.type == pg.KEYDOWN and event.key == self.__quit_on_key_pressed__ and self.selected_util == None) if self.__quit_on_key_pressed__ != None else False):
172
227
  pg.quit()
228
+ if hasattr(self, "__winrecorder__"): self.__winrecorder__.quit()
173
229
  self.quit = True