batframework 1.0.9a11__py3-none-any.whl → 1.0.9a13__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.
Files changed (73) hide show
  1. batFramework/__init__.py +3 -11
  2. batFramework/action.py +280 -279
  3. batFramework/actionContainer.py +105 -82
  4. batFramework/animatedSprite.py +80 -58
  5. batFramework/animation.py +91 -77
  6. batFramework/audioManager.py +156 -131
  7. batFramework/baseScene.py +249 -240
  8. batFramework/camera.py +245 -317
  9. batFramework/constants.py +57 -51
  10. batFramework/cutscene.py +239 -253
  11. batFramework/cutsceneManager.py +34 -34
  12. batFramework/drawable.py +107 -77
  13. batFramework/dynamicEntity.py +30 -30
  14. batFramework/easingController.py +58 -58
  15. batFramework/entity.py +130 -130
  16. batFramework/enums.py +171 -135
  17. batFramework/fontManager.py +65 -65
  18. batFramework/gui/__init__.py +28 -25
  19. batFramework/gui/animatedLabel.py +90 -89
  20. batFramework/gui/button.py +17 -17
  21. batFramework/gui/clickableWidget.py +244 -244
  22. batFramework/gui/collapseContainer.py +98 -0
  23. batFramework/gui/constraints/__init__.py +1 -1
  24. batFramework/gui/constraints/constraints.py +1066 -980
  25. batFramework/gui/container.py +220 -206
  26. batFramework/gui/debugger.py +140 -130
  27. batFramework/gui/draggableWidget.py +63 -44
  28. batFramework/gui/image.py +61 -58
  29. batFramework/gui/indicator.py +116 -113
  30. batFramework/gui/interactiveWidget.py +243 -239
  31. batFramework/gui/label.py +147 -344
  32. batFramework/gui/layout.py +442 -429
  33. batFramework/gui/meter.py +155 -96
  34. batFramework/gui/radioButton.py +43 -35
  35. batFramework/gui/root.py +228 -228
  36. batFramework/gui/scrollingContainer.py +282 -0
  37. batFramework/gui/selector.py +232 -250
  38. batFramework/gui/shape.py +286 -276
  39. batFramework/gui/slider.py +353 -397
  40. batFramework/gui/style.py +10 -10
  41. batFramework/gui/styleManager.py +49 -54
  42. batFramework/gui/syncedVar.py +43 -49
  43. batFramework/gui/textInput.py +331 -306
  44. batFramework/gui/textWidget.py +308 -0
  45. batFramework/gui/toggle.py +140 -128
  46. batFramework/gui/tooltip.py +35 -30
  47. batFramework/gui/widget.py +546 -521
  48. batFramework/manager.py +131 -134
  49. batFramework/particle.py +118 -118
  50. batFramework/propertyEaser.py +79 -79
  51. batFramework/renderGroup.py +34 -34
  52. batFramework/resourceManager.py +130 -130
  53. batFramework/scene.py +31 -31
  54. batFramework/sceneLayer.py +134 -138
  55. batFramework/sceneManager.py +200 -197
  56. batFramework/scrollingSprite.py +115 -115
  57. batFramework/sprite.py +46 -51
  58. batFramework/stateMachine.py +49 -54
  59. batFramework/templates/__init__.py +2 -1
  60. batFramework/templates/character.py +15 -0
  61. batFramework/templates/controller.py +158 -97
  62. batFramework/templates/stateMachine.py +39 -0
  63. batFramework/tileset.py +46 -46
  64. batFramework/timeManager.py +213 -213
  65. batFramework/transition.py +162 -162
  66. batFramework/triggerZone.py +22 -22
  67. batFramework/utils.py +306 -306
  68. {batframework-1.0.9a11.dist-info → batframework-1.0.9a13.dist-info}/LICENSE +20 -20
  69. {batframework-1.0.9a11.dist-info → batframework-1.0.9a13.dist-info}/METADATA +24 -17
  70. batframework-1.0.9a13.dist-info/RECORD +72 -0
  71. batframework-1.0.9a11.dist-info/RECORD +0 -67
  72. {batframework-1.0.9a11.dist-info → batframework-1.0.9a13.dist-info}/WHEEL +0 -0
  73. {batframework-1.0.9a11.dist-info → batframework-1.0.9a13.dist-info}/top_level.txt +0 -0
batFramework/utils.py CHANGED
@@ -1,306 +1,306 @@
1
- import pygame
2
- import batFramework as bf
3
- import math
4
- import random
5
- from .enums import *
6
- import re
7
- from typing import Callable, TYPE_CHECKING
8
- from functools import cache
9
- if TYPE_CHECKING:
10
- from .drawable import Drawable
11
- from .entity import Entity
12
- from pygame.math import Vector2
13
-
14
-
15
- class Singleton(type):
16
- _instances = {}
17
-
18
- def __call__(cls, *args, **kwargs):
19
- if cls not in cls._instances:
20
- cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
21
- return cls._instances[cls]
22
-
23
-
24
- class Utils:
25
-
26
- @staticmethod
27
- def split_surface(
28
- surface: pygame.Surface, split_size: tuple[int, int], func=None
29
- ) -> dict[tuple[int, int], pygame.Surface]:
30
- """
31
- Splits a surface into subsurfaces based on a given size and returns a dictionary of them with their coordinates as keys.
32
-
33
- Args:
34
- surface (pygame.Surface): The surface to be split.
35
- split_size (tuple[int, int]): The size of each subsurface (width, height).
36
- func (callable, optional): A function to apply to each subsurface. Defaults to None.
37
-
38
- Returns:
39
- dict[tuple[int, int], pygame.Surface]: A dictionary with (x, y) coordinates as keys and the corresponding subsurfaces as values.
40
- """
41
- width, height = surface.get_size()
42
- res = {}
43
- for iy, y in enumerate(range(0, height, split_size[1])):
44
- for ix, x in enumerate(range(0, width, split_size[0])):
45
- sub = surface.subsurface((x, y, split_size[0], split_size[1]))
46
-
47
- if func is not None:
48
- sub = func(sub)
49
-
50
- res[(ix, iy)] = sub
51
-
52
- return res
53
-
54
- @staticmethod
55
- def filter_text(text_mode: textMode):
56
- """
57
- Filters a string based on the specified text mode.
58
-
59
- Args:
60
- text_mode (textMode): Mode specifying the type of filtering (ALPHABETICAL, NUMERICAL, ALPHANUMERICAL).
61
-
62
- Returns:
63
- callable: A function that takes a string and removes all characters not allowed by the text mode.
64
-
65
- Raises:
66
- ValueError: If an unsupported text mode is provided.
67
- """
68
-
69
- if text_mode == textMode.ALPHABETICAL:
70
- pattern = re.compile(r"[^a-zA-Z]")
71
- elif text_mode == textMode.NUMERICAL:
72
- pattern = re.compile(r"[^0-9]")
73
- elif text_mode == textMode.ALPHANUMERICAL:
74
- pattern = re.compile(r"[^a-zA-Z0-9]")
75
- else:
76
- raise ValueError("Unsupported text mode")
77
-
78
- def filter_function(s: str) -> str:
79
- return pattern.sub("", s)
80
-
81
- return filter_function
82
-
83
-
84
- @staticmethod
85
- def create_spotlight(inside_color, outside_color, radius, radius_stop=None, dest_surf=None,size=None):
86
- """
87
- Creates a spotlight effect on a surface with a gradient from inside_color to outside_color.
88
-
89
- Args:
90
- inside_color (tuple[int, int, int]): RGB color at the center of the spotlight.
91
- outside_color (tuple[int, int, int]): RGB color at the outer edge of the spotlight.
92
- radius (int): Radius of the inner circle.
93
- radius_stop (int, optional): Radius where the spotlight ends. Defaults to the value of radius.
94
- dest_surf (pygame.Surface, optional): Surface to draw the spotlight on. Defaults to None.
95
- size (tuple[int, int], optional): Size of the surface if dest_surf is None. Defaults to a square based on radius_stop.
96
-
97
- Returns:
98
- pygame.Surface: The surface with the spotlight effect drawn on it.
99
- """
100
-
101
- if radius_stop is None:
102
- radius_stop = radius
103
- diameter = radius_stop * 2
104
-
105
- if dest_surf is None:
106
- if size is None:
107
- size = (diameter,diameter)
108
- dest_surf = pygame.Surface(size, pygame.SRCALPHA)
109
-
110
- dest_surf.fill((0,0,0,0))
111
-
112
-
113
- center = dest_surf.get_rect().center
114
-
115
- if radius_stop != radius:
116
- for r in range(radius_stop, radius - 1, -1):
117
- color = [
118
- inside_color[i] + (outside_color[i] - inside_color[i]) * (r - radius) / (radius_stop - radius)
119
- for i in range(3)
120
- ] + [255] # Preserve the alpha channel as fully opaque
121
- pygame.draw.circle(dest_surf, color, center, r)
122
- else:
123
- pygame.draw.circle(dest_surf, inside_color, center, radius)
124
-
125
- return dest_surf
126
-
127
- @staticmethod
128
- def draw_spotlight(dest_surf:pygame.Surface,inside_color,outside_color,radius,radius_stop=None,center=None):
129
- """
130
- Draws a spotlight effect directly onto an existing surface.
131
-
132
- Args:
133
- dest_surf (pygame.Surface): The surface to draw the spotlight on.
134
- inside_color (tuple[int, int, int]): RGB color at the center of the spotlight.
135
- outside_color (tuple[int, int, int]): RGB color at the outer edge of the spotlight.
136
- radius (int): Radius of the inner circle.
137
- radius_stop (int, optional): Radius where the spotlight ends. Defaults to the value of radius.
138
- center (tuple[int, int], optional): Center point of the spotlight. Defaults to the center of dest_surf.
139
- """
140
-
141
- if radius_stop is None:
142
- radius_stop = radius
143
- center = dest_surf.get_rect().center if center is None else center
144
- if radius_stop != radius:
145
- for r in range(radius_stop, radius - 1, -1):
146
- color = [
147
- inside_color[i] + (outside_color[i] - inside_color[i]) * (r - radius) / (radius_stop - radius)
148
- for i in range(3)
149
- ] + [255]
150
- pygame.draw.circle(dest_surf, color, center, r)
151
- else:
152
- pygame.draw.circle(dest_surf, inside_color, center, radius)
153
-
154
-
155
- @staticmethod
156
- def random_color(min_value: int = 0, max_value: int = 255) -> tuple[int, int, int]:
157
- """
158
- Generates a random color as an RGB tuple.
159
-
160
- Args:
161
- min_value (int): Minimum value for each RGB component (inclusive). Defaults to 0.
162
- max_value (int): Maximum value for each RGB component (inclusive). Defaults to 255.
163
-
164
- Returns:
165
- tuple[int, int, int]: A tuple representing a random color in RGB format, with each component
166
- between min_value and max_value.
167
- """
168
- return random.randint(min_value, max_value), random.randint(min_value, max_value), random.randint(min_value, max_value)
169
-
170
- @staticmethod
171
- def random_point_on_screen(margin: int = 0) -> tuple[int, int]:
172
- """
173
- Generates a random point on the screen, considering a margin from the edges.
174
-
175
- Args:
176
- margin (int): Margin from the screen edges, where the point won't be generated.
177
- If margin is less than 0 or greater than half the screen resolution, returns (0, 0).
178
-
179
- Returns:
180
- tuple[int, int]: A tuple representing a random point (x, y) on the screen within the screen
181
- resolution minus the margin.
182
- """
183
- if margin < 0 or margin > bf.const.RESOLUTION[0]//2 or margin > bf.const.RESOLUTION[1]//2:
184
- return 0, 0
185
- return random.randint(margin, bf.const.RESOLUTION[0] - margin), random.randint(margin, bf.const.RESOLUTION[1] - margin)
186
-
187
- @staticmethod
188
- def distance_point(a:tuple[float,float],b:tuple[float,float]):
189
- return math.sqrt((a[0]-b[0]) ** 2 + (a[1]-b[1])**2)
190
-
191
- @staticmethod
192
- def rotate_point(point: Vector2, angle: float, center: Vector2) -> Vector2:
193
- """Rotate a point around a center by angle (in degrees)."""
194
- rad = math.radians(angle)
195
- translated = point - center
196
- rotated = Vector2(
197
- translated.x * math.cos(rad) - translated.y * math.sin(rad),
198
- translated.x * math.sin(rad) + translated.y * math.cos(rad)
199
- )
200
- return rotated + center
201
-
202
-
203
- def draw_triangle(surface:pygame.Surface, color, rect:pygame.FRect|pygame.Rect, direction:bf.enums.direction=bf.enums.direction.RIGHT,width:int=0):
204
- """
205
- Draw a filled triangle inside a rectangle on a Pygame surface, pointing in the specified direction.
206
-
207
- Args:
208
- surface: The Pygame surface to draw on.
209
- color: The color of the triangle (e.g., (255, 0, 0) for red).
210
- rect: A pygame.Rect object defining the rectangle's position and size.
211
- direction: A string ('up', 'down', 'left', 'right') indicating the triangle's orientation.
212
- """
213
- # Define the three vertices of the triangle based on direction
214
- rect = rect.copy()
215
- rect.inflate_ip(-1,-1)
216
- if direction == direction.UP:
217
- points = [
218
- (rect.left, rect.bottom), # Bottom-left corner
219
- (rect.right, rect.bottom), # Bottom-right corner
220
- (rect.centerx, rect.top) # Top center (apex)
221
- ]
222
- elif direction == direction.DOWN:
223
- points = [
224
- (rect.left, rect.top), # Top-left corner
225
- (rect.right, rect.top), # Top-right corner
226
- (rect.centerx, rect.bottom) # Bottom center (apex)
227
- ]
228
- elif direction == direction.LEFT:
229
- points = [
230
- (rect.right, rect.top), # Top-right corner
231
- (rect.right, rect.bottom), # Bottom-right corner
232
- (rect.left, rect.centery) # Left center (apex)
233
- ]
234
- elif direction == direction.RIGHT:
235
- points = [
236
- (rect.left, rect.top), # Top-left corner
237
- (rect.left, rect.bottom), # Bottom-left corner
238
- (rect.right, rect.centery) # Right center (apex)
239
- ]
240
- else:
241
- raise ValueError("Invalid direction")
242
-
243
- # Draw the filled triangle
244
- pygame.draw.polygon(surface, color, points,width=width)
245
-
246
- def draw_arc_by_points(surface, color, start_pos, end_pos, tightness=0.5, width=1, resolution=0.5,antialias:bool=False):
247
- """
248
- Draw a smooth circular arc connecting start_pos and end_pos.
249
- `tightness` controls curvature: 0 is straight line, 1 is semicircle, higher = more bulge.
250
- Negative tightness flips the bulge direction.
251
-
252
- Args:
253
- surface - pygame Surface
254
- color - RGB or RGBA
255
- start_pos - (x, y)
256
- end_pos - (x, y)
257
- tightness - curvature control, 0 = straight, 1 = half circle
258
- width - line width
259
- resolution - approx pixels per segment
260
- Returns:
261
- pygame.Rect bounding the drawn arc
262
- """
263
- p0 = pygame.Vector2(start_pos)
264
- p1 = pygame.Vector2(end_pos)
265
- chord = p1 - p0
266
- if chord.length_squared() == 0:
267
- if antialias:
268
- return pygame.draw.aacircle(surface, color, p0, width // 2)
269
- return pygame.draw.circle(surface, color, p0, width // 2)
270
-
271
- # Midpoint and perpendicular
272
- mid = (p0 + p1) * 0.5
273
- perp = pygame.Vector2(-chord.y, chord.x).normalize()
274
-
275
- # Distance of center from midpoint, based on tightness
276
- h = chord.length() * tightness
277
- center = mid + perp * h
278
-
279
- # Radius and angles
280
- r = (p0 - center).length()
281
- ang0 = math.atan2(p0.y - center.y, p0.x - center.x)
282
- ang1 = math.atan2(p1.y - center.y, p1.x - center.x)
283
-
284
- # Normalize sweep direction based on sign of tightness
285
- sweep = ang1 - ang0
286
- if tightness > 0 and sweep < 0:
287
- sweep += 2 * math.pi
288
- elif tightness < 0 and sweep > 0:
289
- sweep -= 2 * math.pi
290
-
291
- # Number of points
292
- arc_len = abs(sweep * r)
293
- segs = max(2, int(arc_len / max(resolution, 1)))
294
-
295
- points = []
296
- for i in range(segs + 1):
297
- t = i / segs
298
- a = ang0 + sweep * t
299
- points.append((
300
- center.x + math.cos(a) * r,
301
- center.y + math.sin(a) * r
302
- ))
303
- if antialias:
304
- return pygame.draw.aalines(surface, color, False, points)
305
-
306
- return pygame.draw.lines(surface, color, False, points, width)
1
+ import pygame
2
+ import batFramework as bf
3
+ import math
4
+ import random
5
+ from .enums import *
6
+ import re
7
+ from typing import Callable, TYPE_CHECKING
8
+ from functools import cache
9
+ if TYPE_CHECKING:
10
+ from .drawable import Drawable
11
+ from .entity import Entity
12
+ from pygame.math import Vector2
13
+
14
+
15
+ class Singleton(type):
16
+ _instances = {}
17
+
18
+ def __call__(cls, *args, **kwargs):
19
+ if cls not in cls._instances:
20
+ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
21
+ return cls._instances[cls]
22
+
23
+
24
+ class Utils:
25
+
26
+ @staticmethod
27
+ def split_surface(
28
+ surface: pygame.Surface, split_size: tuple[int, int], func=None
29
+ ) -> dict[tuple[int, int], pygame.Surface]:
30
+ """
31
+ Splits a surface into subsurfaces based on a given size and returns a dictionary of them with their coordinates as keys.
32
+
33
+ Args:
34
+ surface (pygame.Surface): The surface to be split.
35
+ split_size (tuple[int, int]): The size of each subsurface (width, height).
36
+ func (callable, optional): A function to apply to each subsurface. Defaults to None.
37
+
38
+ Returns:
39
+ dict[tuple[int, int], pygame.Surface]: A dictionary with (x, y) coordinates as keys and the corresponding subsurfaces as values.
40
+ """
41
+ width, height = surface.get_size()
42
+ res = {}
43
+ for iy, y in enumerate(range(0, height, split_size[1])):
44
+ for ix, x in enumerate(range(0, width, split_size[0])):
45
+ sub = surface.subsurface((x, y, split_size[0], split_size[1]))
46
+
47
+ if func is not None:
48
+ sub = func(sub)
49
+
50
+ res[(ix, iy)] = sub
51
+
52
+ return res
53
+
54
+ @staticmethod
55
+ def filter_text(text_mode: textMode):
56
+ """
57
+ Filters a string based on the specified text mode.
58
+
59
+ Args:
60
+ text_mode (textMode): Mode specifying the type of filtering (ALPHABETICAL, NUMERICAL, ALPHANUMERICAL).
61
+
62
+ Returns:
63
+ callable: A function that takes a string and removes all characters not allowed by the text mode.
64
+
65
+ Raises:
66
+ ValueError: If an unsupported text mode is provided.
67
+ """
68
+
69
+ if text_mode == textMode.ALPHABETICAL:
70
+ pattern = re.compile(r"[^a-zA-Z]")
71
+ elif text_mode == textMode.NUMERICAL:
72
+ pattern = re.compile(r"[^0-9]")
73
+ elif text_mode == textMode.ALPHANUMERICAL:
74
+ pattern = re.compile(r"[^a-zA-Z0-9]")
75
+ else:
76
+ raise ValueError("Unsupported text mode")
77
+
78
+ def filter_function(s: str) -> str:
79
+ return pattern.sub("", s)
80
+
81
+ return filter_function
82
+
83
+
84
+ @staticmethod
85
+ def create_spotlight(inside_color, outside_color, radius, radius_stop=None, dest_surf=None,size=None):
86
+ """
87
+ Creates a spotlight effect on a surface with a gradient from inside_color to outside_color.
88
+
89
+ Args:
90
+ inside_color (tuple[int, int, int]): RGB color at the center of the spotlight.
91
+ outside_color (tuple[int, int, int]): RGB color at the outer edge of the spotlight.
92
+ radius (int): Radius of the inner circle.
93
+ radius_stop (int, optional): Radius where the spotlight ends. Defaults to the value of radius.
94
+ dest_surf (pygame.Surface, optional): Surface to draw the spotlight on. Defaults to None.
95
+ size (tuple[int, int], optional): Size of the surface if dest_surf is None. Defaults to a square based on radius_stop.
96
+
97
+ Returns:
98
+ pygame.Surface: The surface with the spotlight effect drawn on it.
99
+ """
100
+
101
+ if radius_stop is None:
102
+ radius_stop = radius
103
+ diameter = radius_stop * 2
104
+
105
+ if dest_surf is None:
106
+ if size is None:
107
+ size = (diameter,diameter)
108
+ dest_surf = pygame.Surface(size, pygame.SRCALPHA)
109
+
110
+ dest_surf.fill((0,0,0,0))
111
+
112
+
113
+ center = dest_surf.get_rect().center
114
+
115
+ if radius_stop != radius:
116
+ for r in range(radius_stop, radius - 1, -1):
117
+ color = [
118
+ inside_color[i] + (outside_color[i] - inside_color[i]) * (r - radius) / (radius_stop - radius)
119
+ for i in range(3)
120
+ ] + [255] # Preserve the alpha channel as fully opaque
121
+ pygame.draw.circle(dest_surf, color, center, r)
122
+ else:
123
+ pygame.draw.circle(dest_surf, inside_color, center, radius)
124
+
125
+ return dest_surf
126
+
127
+ @staticmethod
128
+ def draw_spotlight(dest_surf:pygame.Surface,inside_color,outside_color,radius,radius_stop=None,center=None):
129
+ """
130
+ Draws a spotlight effect directly onto an existing surface.
131
+
132
+ Args:
133
+ dest_surf (pygame.Surface): The surface to draw the spotlight on.
134
+ inside_color (tuple[int, int, int]): RGB color at the center of the spotlight.
135
+ outside_color (tuple[int, int, int]): RGB color at the outer edge of the spotlight.
136
+ radius (int): Radius of the inner circle.
137
+ radius_stop (int, optional): Radius where the spotlight ends. Defaults to the value of radius.
138
+ center (tuple[int, int], optional): Center point of the spotlight. Defaults to the center of dest_surf.
139
+ """
140
+
141
+ if radius_stop is None:
142
+ radius_stop = radius
143
+ center = dest_surf.get_rect().center if center is None else center
144
+ if radius_stop != radius:
145
+ for r in range(radius_stop, radius - 1, -1):
146
+ color = [
147
+ inside_color[i] + (outside_color[i] - inside_color[i]) * (r - radius) / (radius_stop - radius)
148
+ for i in range(3)
149
+ ] + [255]
150
+ pygame.draw.circle(dest_surf, color, center, r)
151
+ else:
152
+ pygame.draw.circle(dest_surf, inside_color, center, radius)
153
+
154
+
155
+ @staticmethod
156
+ def random_color(min_value: int = 0, max_value: int = 255) -> tuple[int, int, int]:
157
+ """
158
+ Generates a random color as an RGB tuple.
159
+
160
+ Args:
161
+ min_value (int): Minimum value for each RGB component (inclusive). Defaults to 0.
162
+ max_value (int): Maximum value for each RGB component (inclusive). Defaults to 255.
163
+
164
+ Returns:
165
+ tuple[int, int, int]: A tuple representing a random color in RGB format, with each component
166
+ between min_value and max_value.
167
+ """
168
+ return random.randint(min_value, max_value), random.randint(min_value, max_value), random.randint(min_value, max_value)
169
+
170
+ @staticmethod
171
+ def random_point_on_screen(margin: int = 0) -> tuple[int, int]:
172
+ """
173
+ Generates a random point on the screen, considering a margin from the edges.
174
+
175
+ Args:
176
+ margin (int): Margin from the screen edges, where the point won't be generated.
177
+ If margin is less than 0 or greater than half the screen resolution, returns (0, 0).
178
+
179
+ Returns:
180
+ tuple[int, int]: A tuple representing a random point (x, y) on the screen within the screen
181
+ resolution minus the margin.
182
+ """
183
+ if margin < 0 or margin > bf.const.RESOLUTION[0]//2 or margin > bf.const.RESOLUTION[1]//2:
184
+ return 0, 0
185
+ return random.randint(margin, bf.const.RESOLUTION[0] - margin), random.randint(margin, bf.const.RESOLUTION[1] - margin)
186
+
187
+ @staticmethod
188
+ def distance_point(a:tuple[float,float],b:tuple[float,float]):
189
+ return math.sqrt((a[0]-b[0]) ** 2 + (a[1]-b[1])**2)
190
+
191
+ @staticmethod
192
+ def rotate_point(point: Vector2, angle: float, center: Vector2) -> Vector2:
193
+ """Rotate a point around a center by angle (in degrees)."""
194
+ rad = math.radians(angle)
195
+ translated = point - center
196
+ rotated = Vector2(
197
+ translated.x * math.cos(rad) - translated.y * math.sin(rad),
198
+ translated.x * math.sin(rad) + translated.y * math.cos(rad)
199
+ )
200
+ return rotated + center
201
+
202
+
203
+ def draw_triangle(surface:pygame.Surface, color, rect:pygame.FRect|pygame.Rect, direction:bf.enums.direction=bf.enums.direction.RIGHT,width:int=0):
204
+ """
205
+ Draw a filled triangle inside a rectangle on a Pygame surface, pointing in the specified direction.
206
+
207
+ Args:
208
+ surface: The Pygame surface to draw on.
209
+ color: The color of the triangle (e.g., (255, 0, 0) for red).
210
+ rect: A pygame.Rect object defining the rectangle's position and size.
211
+ direction: A string ('up', 'down', 'left', 'right') indicating the triangle's orientation.
212
+ """
213
+ # Define the three vertices of the triangle based on direction
214
+ rect = rect.copy()
215
+ rect.inflate_ip(-1,-1)
216
+ if direction == direction.UP:
217
+ points = [
218
+ (rect.left, rect.bottom), # Bottom-left corner
219
+ (rect.right, rect.bottom), # Bottom-right corner
220
+ (rect.centerx, rect.top) # Top center (apex)
221
+ ]
222
+ elif direction == direction.DOWN:
223
+ points = [
224
+ (rect.left, rect.top), # Top-left corner
225
+ (rect.right, rect.top), # Top-right corner
226
+ (rect.centerx, rect.bottom) # Bottom center (apex)
227
+ ]
228
+ elif direction == direction.LEFT:
229
+ points = [
230
+ (rect.right, rect.top), # Top-right corner
231
+ (rect.right, rect.bottom), # Bottom-right corner
232
+ (rect.left, rect.centery) # Left center (apex)
233
+ ]
234
+ elif direction == direction.RIGHT:
235
+ points = [
236
+ (rect.left, rect.top), # Top-left corner
237
+ (rect.left, rect.bottom), # Bottom-left corner
238
+ (rect.right, rect.centery) # Right center (apex)
239
+ ]
240
+ else:
241
+ raise ValueError("Invalid direction")
242
+
243
+ # Draw the filled triangle
244
+ pygame.draw.polygon(surface, color, points,width=width)
245
+
246
+ def draw_arc_by_points(surface, color, start_pos, end_pos, tightness=0.5, width=1, resolution=0.5,antialias:bool=False):
247
+ """
248
+ Draw a smooth circular arc connecting start_pos and end_pos.
249
+ `tightness` controls curvature: 0 is straight line, 1 is semicircle, higher = more bulge.
250
+ Negative tightness flips the bulge direction.
251
+
252
+ Args:
253
+ surface - pygame Surface
254
+ color - RGB or RGBA
255
+ start_pos - (x, y)
256
+ end_pos - (x, y)
257
+ tightness - curvature control, 0 = straight, 1 = half circle
258
+ width - line width
259
+ resolution - approx pixels per segment
260
+ Returns:
261
+ pygame.Rect bounding the drawn arc
262
+ """
263
+ p0 = pygame.Vector2(start_pos)
264
+ p1 = pygame.Vector2(end_pos)
265
+ chord = p1 - p0
266
+ if chord.length_squared() == 0:
267
+ if antialias:
268
+ return pygame.draw.aacircle(surface, color, p0, width // 2)
269
+ return pygame.draw.circle(surface, color, p0, width // 2)
270
+
271
+ # Midpoint and perpendicular
272
+ mid = (p0 + p1) * 0.5
273
+ perp = pygame.Vector2(-chord.y, chord.x).normalize()
274
+
275
+ # Distance of center from midpoint, based on tightness
276
+ h = chord.length() * tightness
277
+ center = mid + perp * h
278
+
279
+ # Radius and angles
280
+ r = (p0 - center).length()
281
+ ang0 = math.atan2(p0.y - center.y, p0.x - center.x)
282
+ ang1 = math.atan2(p1.y - center.y, p1.x - center.x)
283
+
284
+ # Normalize sweep direction based on sign of tightness
285
+ sweep = ang1 - ang0
286
+ if tightness > 0 and sweep < 0:
287
+ sweep += 2 * math.pi
288
+ elif tightness < 0 and sweep > 0:
289
+ sweep -= 2 * math.pi
290
+
291
+ # Number of points
292
+ arc_len = abs(sweep * r)
293
+ segs = max(2, int(arc_len / max(resolution, 1)))
294
+
295
+ points = []
296
+ for i in range(segs + 1):
297
+ t = i / segs
298
+ a = ang0 + sweep * t
299
+ points.append((
300
+ center.x + math.cos(a) * r,
301
+ center.y + math.sin(a) * r
302
+ ))
303
+ if antialias:
304
+ return pygame.draw.aalines(surface, color, False, points)
305
+
306
+ return pygame.draw.lines(surface, color, False, points, width)
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) [2023] [TURAN BATURAY]
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) [2023] [TURAN BATURAY]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.