simple-graphics-lib 0.0.6__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.
- simple_graphics/__init__.py +1 -0
- simple_graphics/module.py +497 -0
- simple_graphics_lib-0.0.6.dist-info/METADATA +257 -0
- simple_graphics_lib-0.0.6.dist-info/RECORD +7 -0
- simple_graphics_lib-0.0.6.dist-info/WHEEL +5 -0
- simple_graphics_lib-0.0.6.dist-info/licenses/LICENSE +21 -0
- simple_graphics_lib-0.0.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .module import *
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import os
|
|
2
|
+
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"
|
|
3
|
+
import pygame
|
|
4
|
+
from inspect import signature, currentframe
|
|
5
|
+
from math import pi
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
_shapes = []
|
|
9
|
+
_ontick = []
|
|
10
|
+
_key_funcs = {}
|
|
11
|
+
_key_hold_funcs = {}
|
|
12
|
+
_mouse_click_funcs = {}
|
|
13
|
+
_bgcolor = "white"
|
|
14
|
+
_font = "Arial"
|
|
15
|
+
|
|
16
|
+
pygame.init()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Mouse:
|
|
20
|
+
@property
|
|
21
|
+
def x(self):
|
|
22
|
+
return pygame.mouse.get_pos()[0]
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def y(self):
|
|
26
|
+
return pygame.mouse.get_pos()[1]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
mouse = Mouse()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Shape:
|
|
33
|
+
def __init__(self, x, y, color="black", outline=False):
|
|
34
|
+
self.x = x
|
|
35
|
+
self.y = y
|
|
36
|
+
self.color = color
|
|
37
|
+
self.outline = int(outline)
|
|
38
|
+
_shapes.append(self)
|
|
39
|
+
|
|
40
|
+
def __str__(self):
|
|
41
|
+
return self.__class__.__name__.lower()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Rect(Shape):
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
x: int,
|
|
48
|
+
y: int,
|
|
49
|
+
width: int = 10,
|
|
50
|
+
height: int = 10,
|
|
51
|
+
color: str = "black",
|
|
52
|
+
outline: bool = False,
|
|
53
|
+
):
|
|
54
|
+
super().__init__(x, y, color, outline)
|
|
55
|
+
self.width = width
|
|
56
|
+
self.height = height
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def rect(self):
|
|
60
|
+
return (self.x, self.y, self.width, self.height)
|
|
61
|
+
|
|
62
|
+
def is_obj_over(self, ox=None, oy=None):
|
|
63
|
+
if ox == None:
|
|
64
|
+
ox = pygame.mouse.get_pos()[0]
|
|
65
|
+
if oy == None:
|
|
66
|
+
oy = pygame.mouse.get_pos()[1]
|
|
67
|
+
x_over = ox > self.x and ox < self.x + self.height
|
|
68
|
+
y_over = oy > self.y and oy < self.y + self.width
|
|
69
|
+
return x_over and y_over
|
|
70
|
+
|
|
71
|
+
def get_area(self):
|
|
72
|
+
return self.width * self.height
|
|
73
|
+
|
|
74
|
+
def get_perimeter(self):
|
|
75
|
+
return (2 * self.width) + (2 * self.height)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Circle(Shape):
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
x: int,
|
|
82
|
+
y: int,
|
|
83
|
+
radius: int = 10,
|
|
84
|
+
color: str = "black",
|
|
85
|
+
outline: bool = False,
|
|
86
|
+
):
|
|
87
|
+
super().__init__(x, y, color, outline)
|
|
88
|
+
self.radius = radius
|
|
89
|
+
|
|
90
|
+
def is_obj_over(self, ox=None, oy=None):
|
|
91
|
+
if ox == None:
|
|
92
|
+
ox = pygame.mouse.get_pos()[0]
|
|
93
|
+
if oy == None:
|
|
94
|
+
oy = pygame.mouse.get_pos()[1]
|
|
95
|
+
inside = (ox - self.x) ** 2 + (oy - self.y) ** 2 < self.radius**2
|
|
96
|
+
return inside
|
|
97
|
+
|
|
98
|
+
def get_area(self):
|
|
99
|
+
return pi * self.radius ^ 2
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Polygon(Shape):
|
|
103
|
+
def __init__(
|
|
104
|
+
self, points: list[tuple], color: str = "black", outline: bool = False
|
|
105
|
+
):
|
|
106
|
+
self.color = color
|
|
107
|
+
self.points = points
|
|
108
|
+
self.outline = int(outline)
|
|
109
|
+
_shapes.append(self)
|
|
110
|
+
|
|
111
|
+
def is_obj_over(self, ox=None, oy=None):
|
|
112
|
+
if ox == None:
|
|
113
|
+
ox = pygame.mouse.get_pos()[0]
|
|
114
|
+
if oy == None:
|
|
115
|
+
oy = pygame.mouse.get_pos()[1]
|
|
116
|
+
|
|
117
|
+
n = len(self.points)
|
|
118
|
+
inside = False
|
|
119
|
+
|
|
120
|
+
p1x, p1y = self.points[0]
|
|
121
|
+
for i in range(n + 1):
|
|
122
|
+
p2x, p2y = self.points[i % n]
|
|
123
|
+
if oy > min(p1y, p2y):
|
|
124
|
+
if oy <= max(p1y, p2y):
|
|
125
|
+
if ox <= max(p1x, p2x):
|
|
126
|
+
if p1y != p2y:
|
|
127
|
+
xints = (oy - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
|
|
128
|
+
if p1x == p2x or ox <= xints:
|
|
129
|
+
inside = not inside
|
|
130
|
+
p1x, p1y = p2x, p2y
|
|
131
|
+
return inside
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class Line(Shape):
|
|
135
|
+
def __init__(self, points: list[tuple], width: int = 10, color: str = "black"):
|
|
136
|
+
self.color = color
|
|
137
|
+
self.points = points
|
|
138
|
+
self.width = width
|
|
139
|
+
_shapes.append(self)
|
|
140
|
+
|
|
141
|
+
def is_obj_over(self, ox=None, oy=None):
|
|
142
|
+
if ox == None:
|
|
143
|
+
ox = pygame.mouse.get_pos()[0]
|
|
144
|
+
if oy == None:
|
|
145
|
+
oy = pygame.mouse.get_pos()[1]
|
|
146
|
+
|
|
147
|
+
for i in range(len(self.points) - 1):
|
|
148
|
+
x1, y1 = self.points[i]
|
|
149
|
+
x2, y2 = self.points[i + 1]
|
|
150
|
+
|
|
151
|
+
dist = self._distance_to_segment(ox, oy, x1, y1, x2, y2)
|
|
152
|
+
|
|
153
|
+
if dist <= self.width / 2:
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
def _distance_to_segment(self, px, py, x1, y1, x2, y2):
|
|
159
|
+
dx = x2 - x1
|
|
160
|
+
dy = y2 - y1
|
|
161
|
+
|
|
162
|
+
if dx == 0 and dy == 0:
|
|
163
|
+
return ((px - x1) ** 2 + (py - y1) ** 2) ** 0.5
|
|
164
|
+
|
|
165
|
+
t = max(0, min(1, ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy)))
|
|
166
|
+
|
|
167
|
+
closest_x = x1 + t * dx
|
|
168
|
+
closest_y = y1 + t * dy
|
|
169
|
+
|
|
170
|
+
return ((px - closest_x) ** 2 + (py - closest_y) ** 2) ** 0.5
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class Text(Shape):
|
|
174
|
+
def __init__(
|
|
175
|
+
self,
|
|
176
|
+
x: int,
|
|
177
|
+
y: int,
|
|
178
|
+
text: str,
|
|
179
|
+
color: str = "black",
|
|
180
|
+
font=_font,
|
|
181
|
+
size: int = 36,
|
|
182
|
+
):
|
|
183
|
+
super().__init__(x, y, color)
|
|
184
|
+
self.text = text
|
|
185
|
+
self.size = size
|
|
186
|
+
self.font = pygame.font.SysFont(font, size)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def txtsurf(self):
|
|
190
|
+
return self.font.render(str(self.text), True, self.color)
|
|
191
|
+
|
|
192
|
+
def is_obj_over(self, ox=None, oy=None):
|
|
193
|
+
if ox == None:
|
|
194
|
+
ox = pygame.mouse.get_pos()[0]
|
|
195
|
+
if oy == None:
|
|
196
|
+
oy = pygame.mouse.get_pos()[1]
|
|
197
|
+
|
|
198
|
+
text_width = self.txtsurf.get_width()
|
|
199
|
+
text_height = self.txtsurf.get_height()
|
|
200
|
+
|
|
201
|
+
x_over = ox > self.x and ox < self.x + text_width
|
|
202
|
+
y_over = oy > self.y and oy < self.y + text_height
|
|
203
|
+
return x_over and y_over
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class CollisionManager:
|
|
207
|
+
@staticmethod
|
|
208
|
+
def rect_rect(rect1: Rect, rect2: Rect):
|
|
209
|
+
return pygame.Rect(*rect1.rect).colliderect(pygame.Rect(*rect2.rect))
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def rect_circle(rect: Rect, circle: Circle):
|
|
213
|
+
closest_x = max(rect.x, min(circle.x, rect.x + rect.width))
|
|
214
|
+
closest_y = max(rect.y, min(circle.y, rect.y + rect.height))
|
|
215
|
+
dist = ((circle.x - closest_x) ** 2 + (circle.y - closest_y) ** 2) ** 0.5
|
|
216
|
+
return dist < circle.radius
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def circle_circle(circle1: Circle, circle2: Circle):
|
|
220
|
+
dist = ((circle1.x - circle2.x) ** 2 + (circle1.y - circle2.y) ** 2) ** 0.5
|
|
221
|
+
return dist < circle1.radius + circle2.radius
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def rect_polygon(rect: Rect, polygon: Polygon):
|
|
225
|
+
corners = [
|
|
226
|
+
(rect.x, rect.y),
|
|
227
|
+
(rect.x + rect.width, rect.y),
|
|
228
|
+
(rect.x, rect.y + rect.height),
|
|
229
|
+
(rect.x + rect.width, rect.y + rect.height),
|
|
230
|
+
]
|
|
231
|
+
for cx, cy in corners:
|
|
232
|
+
if polygon.is_obj_over(cx, cy):
|
|
233
|
+
return True
|
|
234
|
+
for px, py in polygon.points:
|
|
235
|
+
if rect.is_obj_over(px, py):
|
|
236
|
+
return True
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def circle_polygon(circle: Circle, polygon: Polygon):
|
|
241
|
+
if polygon.is_obj_over(circle.x, circle.y):
|
|
242
|
+
return True
|
|
243
|
+
for px, py in polygon.points:
|
|
244
|
+
dist = ((circle.x - px) ** 2 + (circle.y - py) ** 2) ** 0.5
|
|
245
|
+
if dist < circle.radius:
|
|
246
|
+
return True
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def rect_text(rect, text):
|
|
251
|
+
text_width = text.txtsurf.get_width()
|
|
252
|
+
text_height = text.txtsurf.get_height()
|
|
253
|
+
return pygame.Rect(*rect.rect).colliderect(
|
|
254
|
+
pygame.Rect(text.x, text.y, text_width, text_height)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def circle_text(circle, text):
|
|
259
|
+
text_width = text.txtsurf.get_width()
|
|
260
|
+
text_height = text.txtsurf.get_height()
|
|
261
|
+
closest_x = max(text.x, min(circle.x, text.x + text_width))
|
|
262
|
+
closest_y = max(text.y, min(circle.y, text.y + text_height))
|
|
263
|
+
dist = ((circle.x - closest_x) ** 2 + (circle.y - closest_y) ** 2) ** 0.5
|
|
264
|
+
return dist < circle.radius
|
|
265
|
+
|
|
266
|
+
def no_collsion_method(shape1, shape2):
|
|
267
|
+
raise Exception(f"{type(shape1)} and {type(shape2)} have no collision method")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def check_args(func, name):
|
|
271
|
+
sig = signature(func)
|
|
272
|
+
param_names = list(sig.parameters)
|
|
273
|
+
if str(sig) != "()":
|
|
274
|
+
raise ValueError(
|
|
275
|
+
f'Functions defined with the @{name} decorator may not take arguments\n but "{func.__name__}" was defined with {param_names}'
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
#decorartors like this get weird: if you want to be able to pass arguments in you need to handle 2 cases
|
|
279
|
+
#if theres no arguments then it just takes the function as the argument
|
|
280
|
+
def on_tick(func: Callable):
|
|
281
|
+
name = currentframe().f_code.co_name
|
|
282
|
+
check_args(func, name)
|
|
283
|
+
_ontick.append(func)
|
|
284
|
+
return func
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def on_press(target: Callable | str):
|
|
288
|
+
if callable(target):
|
|
289
|
+
func = target
|
|
290
|
+
name = currentframe().f_code.co_name
|
|
291
|
+
sig = signature(func)
|
|
292
|
+
param_names = list(sig.parameters)
|
|
293
|
+
if len(param_names) not in [0, 1]:
|
|
294
|
+
raise ValueError(
|
|
295
|
+
f'Functions defined with the @{name} decorator may take 0 or 1 arguments\n but "{func.__name__}" was defined with {param_names}'
|
|
296
|
+
)
|
|
297
|
+
func._accepts_key = len(param_names) == 1
|
|
298
|
+
_key_funcs[None] = func
|
|
299
|
+
return func
|
|
300
|
+
|
|
301
|
+
else:
|
|
302
|
+
|
|
303
|
+
def decorator(func):
|
|
304
|
+
key = target
|
|
305
|
+
name = currentframe().f_code.co_name
|
|
306
|
+
sig = signature(func)
|
|
307
|
+
param_names = list(sig.parameters)
|
|
308
|
+
if len(param_names) not in [0, 1]:
|
|
309
|
+
raise ValueError(
|
|
310
|
+
f'Functions defined with the @{name} decorator may take 0 or 1 arguments\n but "{func.__name__}" was defined with {param_names}'
|
|
311
|
+
)
|
|
312
|
+
func._accepts_key = len(param_names) == 1
|
|
313
|
+
|
|
314
|
+
keys = [k.strip() for k in key.split(",")]
|
|
315
|
+
for k in keys:
|
|
316
|
+
key_name = k if len(k) == 1 else k.upper()
|
|
317
|
+
pygame_key = getattr(pygame, f"K_{key_name}")
|
|
318
|
+
_key_funcs[pygame_key] = func
|
|
319
|
+
return func
|
|
320
|
+
|
|
321
|
+
return decorator
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def on_hold(target: Callable | str):
|
|
325
|
+
if callable(target):
|
|
326
|
+
func = target
|
|
327
|
+
name = currentframe().f_code.co_name
|
|
328
|
+
sig = signature(func)
|
|
329
|
+
param_names = list(sig.parameters)
|
|
330
|
+
if len(param_names) not in [0, 1]:
|
|
331
|
+
raise ValueError(
|
|
332
|
+
f'Functions defined with the @{name} decorator may take 0 or 1 arguments\n but "{func.__name__}" was defined with {param_names}'
|
|
333
|
+
)
|
|
334
|
+
func._accepts_key = len(param_names) == 1
|
|
335
|
+
_key_hold_funcs[None] = func
|
|
336
|
+
return func
|
|
337
|
+
|
|
338
|
+
else:
|
|
339
|
+
|
|
340
|
+
def decorator(func):
|
|
341
|
+
key = target
|
|
342
|
+
name = currentframe().f_code.co_name
|
|
343
|
+
sig = signature(func)
|
|
344
|
+
param_names = list(sig.parameters)
|
|
345
|
+
if len(param_names) not in [0, 1]:
|
|
346
|
+
raise ValueError(
|
|
347
|
+
f'Functions defined with the @{name} decorator may take 0 or 1 arguments\n but "{func.__name__}" was defined with {param_names}'
|
|
348
|
+
)
|
|
349
|
+
func._accepts_key = len(param_names) == 1
|
|
350
|
+
|
|
351
|
+
keys = [k.strip() for k in key.split(",")]
|
|
352
|
+
for k in keys:
|
|
353
|
+
key_name = k if len(k) == 1 else k.upper()
|
|
354
|
+
pygame_key = getattr(pygame, f"K_{key_name}")
|
|
355
|
+
_key_hold_funcs[pygame_key] = func
|
|
356
|
+
return func
|
|
357
|
+
|
|
358
|
+
return decorator
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def on_click(target: Callable | Shape):
|
|
362
|
+
|
|
363
|
+
if callable(target):
|
|
364
|
+
name = currentframe().f_code.co_name
|
|
365
|
+
check_args(target, name)
|
|
366
|
+
_mouse_click_funcs[None] = target
|
|
367
|
+
return target
|
|
368
|
+
else:
|
|
369
|
+
|
|
370
|
+
def decorator(func):
|
|
371
|
+
name = currentframe().f_code.co_name
|
|
372
|
+
check_args(func, name)
|
|
373
|
+
_mouse_click_funcs[target] = func
|
|
374
|
+
return func
|
|
375
|
+
|
|
376
|
+
return decorator
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def set_bg(color: str):
|
|
380
|
+
global _bgcolor
|
|
381
|
+
_bgcolor = color
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def clear_screen():
|
|
385
|
+
_shapes.clear()
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def is_colliding(shape1: Shape, shape2: Shape) -> bool:
|
|
389
|
+
method_name = f"{str(shape1)}_{str(shape2)}"
|
|
390
|
+
method = getattr(CollisionManager, method_name, None)
|
|
391
|
+
if method is None:
|
|
392
|
+
method_name = f"{str(shape2)}_{str(shape1)}"
|
|
393
|
+
method = getattr(CollisionManager, method_name, None)
|
|
394
|
+
if method is None:
|
|
395
|
+
raise Exception(
|
|
396
|
+
f"{type(shape1).__name__} and {type(shape2).__name__} have no collision method"
|
|
397
|
+
)
|
|
398
|
+
return method(shape2, shape1)
|
|
399
|
+
return method(shape1, shape2)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def run(
|
|
403
|
+
width: int = 200,
|
|
404
|
+
height: int = 200,
|
|
405
|
+
resizable: bool = True,
|
|
406
|
+
caption: str = "SG window",
|
|
407
|
+
):
|
|
408
|
+
|
|
409
|
+
global _bgcolor
|
|
410
|
+
|
|
411
|
+
if resizable:
|
|
412
|
+
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
|
|
413
|
+
else:
|
|
414
|
+
screen = pygame.display.set_mode((width, height))
|
|
415
|
+
|
|
416
|
+
pygame.display.set_caption(caption)
|
|
417
|
+
clock = pygame.time.Clock()
|
|
418
|
+
running = True
|
|
419
|
+
|
|
420
|
+
def handle_events():
|
|
421
|
+
nonlocal running
|
|
422
|
+
|
|
423
|
+
for event in pygame.event.get():
|
|
424
|
+
if event.type == pygame.QUIT:
|
|
425
|
+
running = False
|
|
426
|
+
if event.type == pygame.KEYDOWN:
|
|
427
|
+
if None in _key_funcs:
|
|
428
|
+
func = _key_funcs[None]
|
|
429
|
+
if hasattr(func, "_accepts_key") and func._accepts_key:
|
|
430
|
+
func(pygame.key.name(event.key))
|
|
431
|
+
else:
|
|
432
|
+
func()
|
|
433
|
+
if event.key in _key_funcs.keys():
|
|
434
|
+
func = _key_funcs[event.key]
|
|
435
|
+
if hasattr(func, "_accepts_key") and func._accepts_key:
|
|
436
|
+
func(pygame.key.name(event.key))
|
|
437
|
+
else:
|
|
438
|
+
func()
|
|
439
|
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
440
|
+
if None in _mouse_click_funcs:
|
|
441
|
+
_mouse_click_funcs[None]()
|
|
442
|
+
|
|
443
|
+
for shape in _shapes:
|
|
444
|
+
if shape.is_obj_over() and shape in _mouse_click_funcs:
|
|
445
|
+
_mouse_click_funcs[shape]()
|
|
446
|
+
|
|
447
|
+
keys_pressed = pygame.key.get_pressed()
|
|
448
|
+
for pygame_key, func in _key_hold_funcs.items():
|
|
449
|
+
if pygame_key is None:
|
|
450
|
+
if hasattr(func, "_accepts_key") and func._accepts_key:
|
|
451
|
+
func(pygame.key.name(event.key))
|
|
452
|
+
else:
|
|
453
|
+
func()
|
|
454
|
+
elif keys_pressed[pygame_key]:
|
|
455
|
+
if hasattr(func, "_accepts_key") and func._accepts_key:
|
|
456
|
+
func(pygame.key.name(pygame_key))
|
|
457
|
+
else:
|
|
458
|
+
func()
|
|
459
|
+
|
|
460
|
+
for func in _ontick:
|
|
461
|
+
func()
|
|
462
|
+
|
|
463
|
+
while running:
|
|
464
|
+
handle_events()
|
|
465
|
+
|
|
466
|
+
screen.fill(_bgcolor)
|
|
467
|
+
|
|
468
|
+
for shape in _shapes:
|
|
469
|
+
if str(shape) == "circle":
|
|
470
|
+
pygame.draw.circle(
|
|
471
|
+
screen, shape.color, (shape.x, shape.y), shape.radius, shape.outline
|
|
472
|
+
)
|
|
473
|
+
elif str(shape) == "rect":
|
|
474
|
+
pygame.draw.rect(
|
|
475
|
+
screen,
|
|
476
|
+
shape.color,
|
|
477
|
+
shape.rect,
|
|
478
|
+
shape.outline,
|
|
479
|
+
)
|
|
480
|
+
elif str(shape) == "polygon":
|
|
481
|
+
pygame.draw.polygon(screen, shape.color, shape.points, shape.outline)
|
|
482
|
+
elif str(shape) == "line":
|
|
483
|
+
for i in range(1, len(shape.points)):
|
|
484
|
+
pygame.draw.line(
|
|
485
|
+
screen,
|
|
486
|
+
shape.color,
|
|
487
|
+
shape.points[i - 1],
|
|
488
|
+
shape.points[i],
|
|
489
|
+
shape.width,
|
|
490
|
+
)
|
|
491
|
+
elif str(shape) == "text":
|
|
492
|
+
screen.blit(shape.txtsurf, (shape.x, shape.y))
|
|
493
|
+
|
|
494
|
+
pygame.display.flip()
|
|
495
|
+
clock.tick(60)
|
|
496
|
+
|
|
497
|
+
pygame.quit()
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simple-graphics-lib
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: A simple graphics library for python learners
|
|
5
|
+
Author-email: Max-Hillyer <maxhillyer7012@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Max-Hillyer/Simple_graphics
|
|
8
|
+
Project-URL: Issues, https://github.com/Max-Hillyer/Simple_graphics/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pygame
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Simple Graphics
|
|
18
|
+
---
|
|
19
|
+
simple graphics is a lightweight python graphics package that offers a more readable alternative to pygame
|
|
20
|
+
|
|
21
|
+
simple graphics is meant to be extremely readable, allowing you to focus on the logic of your code instead of graphic specifics
|
|
22
|
+
|
|
23
|
+
## Shapes
|
|
24
|
+
|
|
25
|
+
Simple Graphics offers support for various shapes, that will automatically appear on the screen when defined
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
### Rect
|
|
29
|
+
Arguments:
|
|
30
|
+
| Argument | Type | Role | Default |
|
|
31
|
+
| :--- | :---: | :---: | ---: |
|
|
32
|
+
| x | int | The x position of the rect | None |
|
|
33
|
+
| y | int | The y position of the rect | None |
|
|
34
|
+
| width | int | the width of the rect | 10 |
|
|
35
|
+
| height | int | the height of the rect | 10 |
|
|
36
|
+
| color | str | the color of the rect | "black" |
|
|
37
|
+
| outline | bool | whether the rect is just an outline | False |
|
|
38
|
+
|
|
39
|
+
Methods:
|
|
40
|
+
| Method | Purpose | Arguments | Returns |
|
|
41
|
+
| :--- | :---: | :---: | ---: |
|
|
42
|
+
| is_obj_over | determines if a point is in the rectangle | x: int, y:int | bool |
|
|
43
|
+
| get_area | returns the area of the rectangle| None | int |
|
|
44
|
+
| get_perimeter | returns the perimeter of the rectangle | None | int |
|
|
45
|
+
|
|
46
|
+
### Circle
|
|
47
|
+
Arguments:
|
|
48
|
+
| Argument | Type | Role | Default |
|
|
49
|
+
| :--- | :---: | :---: | ---: |
|
|
50
|
+
| x | int | the x position of the circle | None |
|
|
51
|
+
| y | int | the y position of the circle | None |
|
|
52
|
+
| radius | int | the radius of the circle | 10 |
|
|
53
|
+
| color | str | the color of the circle | "black" |
|
|
54
|
+
| outline | bool | whether the circle is just an outline | False |
|
|
55
|
+
|
|
56
|
+
Methods:
|
|
57
|
+
| Method | Purpose | Arguments | Returns |
|
|
58
|
+
| :--- | :---: | :---: | ---: |
|
|
59
|
+
| is_obj_over | determines if a point is in the circle | x: int, y:int | bool |
|
|
60
|
+
| get_area | returns the area of the circle | None | bool |
|
|
61
|
+
|
|
62
|
+
### Polygon
|
|
63
|
+
Arguments:
|
|
64
|
+
| Argument | Type | Role | Default |
|
|
65
|
+
| :--- | :---: | :---: | ---: |
|
|
66
|
+
| points | list[tuple] | the vertices of the polygon | None |
|
|
67
|
+
| color | str | the color of the polygon | "black" |
|
|
68
|
+
| outline | bool | whther the polygon is just an outline | False |
|
|
69
|
+
|
|
70
|
+
Methods:
|
|
71
|
+
| Method | Purpose | Arguments | Returns |
|
|
72
|
+
| :--- | :---: | :---: | ---: |
|
|
73
|
+
| is_obj_over | determines if a point is in the polygon | x: int, y:int | bool |
|
|
74
|
+
|
|
75
|
+
### Line
|
|
76
|
+
Arguments:
|
|
77
|
+
| Argument | Type | Role | Default |
|
|
78
|
+
| :--- | :---: | :---: | ---: |
|
|
79
|
+
| points | list[tuple] |the points that the line will connect | None |
|
|
80
|
+
| width | int | the width of the line | 10 |
|
|
81
|
+
| color | str | the color of the line | "black" |
|
|
82
|
+
|
|
83
|
+
Methods:
|
|
84
|
+
| Method | Purpose | Arguments | Returns |
|
|
85
|
+
| :--- | :---: | :---: | ---: |
|
|
86
|
+
| is_obj_over | determines if a point is over the line | x: int, y:int | bool |
|
|
87
|
+
|
|
88
|
+
### Text
|
|
89
|
+
Arguments:
|
|
90
|
+
| Argument | Type | Role | Default |
|
|
91
|
+
| :--- | :---: | :---: | ---: |
|
|
92
|
+
| x | int | the x value of the text | None |
|
|
93
|
+
| y | int | the y value of the text | None |
|
|
94
|
+
| text | str | the text of the text | None |
|
|
95
|
+
| color | str | the color of the text | "black" |
|
|
96
|
+
| size | int | the size of the text | 36 |
|
|
97
|
+
|
|
98
|
+
Methods:
|
|
99
|
+
| Method | Purpose | Arguments | Returns |
|
|
100
|
+
| :--- | :---: | :---: | ---: |
|
|
101
|
+
| is_obj_over | determines if a point is in the text | x: int, y:int | bool |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Run
|
|
106
|
+
`run()` is Simple Graphics main function.
|
|
107
|
+
it creates a screen where all defined objects appear and can be interacted with.
|
|
108
|
+
|
|
109
|
+
Arguments:
|
|
110
|
+
| Argument | Type | Role | Default |
|
|
111
|
+
| :--- | :---: | :---: | ---: |
|
|
112
|
+
| width | int | The screen width in pixels | 200 |
|
|
113
|
+
| height | int | the screen height in pixels | 200 |
|
|
114
|
+
| resizable | bool | whether or not the screen is resizable via dragging | True |
|
|
115
|
+
| caption | str | the name of the graphics window | "SG window" |
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## Collisions
|
|
119
|
+
|
|
120
|
+
to check for a collision between 2 shapes, use the `is_colliding()` method:
|
|
121
|
+
```python
|
|
122
|
+
rect1 = Rect(10, 10)
|
|
123
|
+
ball = Circle(10 , 5)
|
|
124
|
+
print(is_colliding(ball, rect1)) # True
|
|
125
|
+
```
|
|
126
|
+
---
|
|
127
|
+
## Events
|
|
128
|
+
|
|
129
|
+
Instead of a for loop, Simple Graphics handles events using decorators
|
|
130
|
+
|
|
131
|
+
### on_tick
|
|
132
|
+
The `on_tick` decorator runs the function every frame
|
|
133
|
+
The default framerate is 60 FPS
|
|
134
|
+
Functions with the `on_tick` decorator may not take arguments
|
|
135
|
+
```python
|
|
136
|
+
@on_tick
|
|
137
|
+
def main():
|
|
138
|
+
do_something()
|
|
139
|
+
|
|
140
|
+
run()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### on_press
|
|
144
|
+
|
|
145
|
+
the `on_press` runs the decorator whenever a specified button is pressed, or when any button is pressed if none are specified
|
|
146
|
+
|
|
147
|
+
The function below will run whenever w or s is pressed
|
|
148
|
+
```python
|
|
149
|
+
@on_press("w,s")
|
|
150
|
+
def do_w():
|
|
151
|
+
do_soemthing()
|
|
152
|
+
|
|
153
|
+
run()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The function below will run whenever any key is pressed
|
|
157
|
+
```python
|
|
158
|
+
@on_press
|
|
159
|
+
def any_key():
|
|
160
|
+
do_something()
|
|
161
|
+
|
|
162
|
+
run()
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Functions with the `@on_press` decorator may only take 1 argument, which will show up as the key pressed
|
|
166
|
+
```python
|
|
167
|
+
@on_press
|
|
168
|
+
def print_key(key):
|
|
169
|
+
print(key)
|
|
170
|
+
|
|
171
|
+
run()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### on_hold
|
|
175
|
+
|
|
176
|
+
the `on_hold` decorator functions exactly like the `on_press` decorator, but the function runs every frame for as long as the key is held, instead of just once
|
|
177
|
+
|
|
178
|
+
The function below will run for as long as w or s is pressed
|
|
179
|
+
```python
|
|
180
|
+
@on_hold("w,s")
|
|
181
|
+
def do_w():
|
|
182
|
+
do_soemthing()
|
|
183
|
+
|
|
184
|
+
run()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The function below will run while any key is pressed
|
|
188
|
+
```python
|
|
189
|
+
@on_press
|
|
190
|
+
def any_key():
|
|
191
|
+
do_something()
|
|
192
|
+
|
|
193
|
+
run()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Functions with the `@on_hold` decorator may only take 1 argument, which will show up as the key held
|
|
197
|
+
```python
|
|
198
|
+
@on_press
|
|
199
|
+
def print_key(key):
|
|
200
|
+
print(key)
|
|
201
|
+
|
|
202
|
+
run()
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### on_click
|
|
206
|
+
|
|
207
|
+
functions defined with the `on_click` decorator will run whenever the specified object is clicked, or whenever any click is detected if none is specified
|
|
208
|
+
|
|
209
|
+
the function below will run whenever the button rect is clicked
|
|
210
|
+
```python
|
|
211
|
+
button = Rect(10, 10)
|
|
212
|
+
@on_click(button)
|
|
213
|
+
def click_button():
|
|
214
|
+
print("button clicked")
|
|
215
|
+
|
|
216
|
+
run()
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
the function below will run when there is any click at all
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
@on_click
|
|
223
|
+
def print_click():
|
|
224
|
+
print("clicked!")
|
|
225
|
+
|
|
226
|
+
run()
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
## The mouse
|
|
231
|
+
A mouse object is already initialized in the Simple Graphics import
|
|
232
|
+
|
|
233
|
+
to get mouse coordinates, simply use `mouse.x` and `mouse.y`
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
```python
|
|
237
|
+
@on_tick
|
|
238
|
+
def main():
|
|
239
|
+
print(mouse.x, mouse.y)
|
|
240
|
+
|
|
241
|
+
run()
|
|
242
|
+
```
|
|
243
|
+
---
|
|
244
|
+
## Miscellaneous Functions
|
|
245
|
+
|
|
246
|
+
### set_bg
|
|
247
|
+
`set_bg()` simply sets the background to the specified color
|
|
248
|
+
|
|
249
|
+
the default background color is white
|
|
250
|
+
```python
|
|
251
|
+
set_bg("black")
|
|
252
|
+
```
|
|
253
|
+
changes the background color to black
|
|
254
|
+
|
|
255
|
+
### clear_screen
|
|
256
|
+
`clear_screen()` clears the screen.
|
|
257
|
+
objects defined before a screen clear can no longer be interacted with
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
simple_graphics/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
|
|
2
|
+
simple_graphics/module.py,sha256=xOZ88TvHBZwjaQ5T67_1IBgc5Y-vSQmF2IS1IsRNgWE,15110
|
|
3
|
+
simple_graphics_lib-0.0.6.dist-info/licenses/LICENSE,sha256=6L13tZqbSqIm3fN66mTM7SRf3aYrxaQF8hEZSRDfDmU,1068
|
|
4
|
+
simple_graphics_lib-0.0.6.dist-info/METADATA,sha256=cfK1W8MdXM8TmpeJUZFGDfWs9sb7z8dSHMOG-0RAb5Y,6807
|
|
5
|
+
simple_graphics_lib-0.0.6.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
simple_graphics_lib-0.0.6.dist-info/top_level.txt,sha256=gJYGGq0qKokydvVsxuGl4VHrAmHRZG11qhQDerRTYdI,16
|
|
7
|
+
simple_graphics_lib-0.0.6.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Max Hillyer
|
|
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
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
simple_graphics
|