Max-Hillyer 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: Max-Hillyer
3
+ Version: 0.0.4
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/pypa/sampleproject
8
+ Project-URL: Issues, https://github.com/pypa/sampleproject/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,241 @@
1
+ # Simple Graphics
2
+ ---
3
+ simple graphics is a lightweight python graphics package that offers a more readable alternative to pygame
4
+
5
+ simple graphics is meant to be extremely readable, allowing you to focus on the logic of your code instead of graphic specifics
6
+
7
+ ## Shapes
8
+
9
+ Simple Graphics offers support for various shapes, that will automatically appear on the screen when defined
10
+
11
+ ---
12
+ ### Rect
13
+ Arguments:
14
+ | Argument | Type | Role | Default |
15
+ | :--- | :---: | :---: | ---: |
16
+ | x | int | The x position of the rect | None |
17
+ | y | int | The y position of the rect | None |
18
+ | width | int | the width of the rect | 10 |
19
+ | height | int | the height of the rect | 10 |
20
+ | color | str | the color of the rect | "black" |
21
+ | outline | bool | whether the rect is just an outline | False |
22
+
23
+ Methods:
24
+ | Method | Purpose | Arguments | Returns |
25
+ | :--- | :---: | :---: | ---: |
26
+ | is_obj_over | determines if a point is in the rectangle | x: int, y:int | bool |
27
+ | get_area | returns the area of the rectangle| None | int |
28
+ | get_perimeter | returns the perimeter of the rectangle | None | int |
29
+
30
+ ### Circle
31
+ Arguments:
32
+ | Argument | Type | Role | Default |
33
+ | :--- | :---: | :---: | ---: |
34
+ | x | int | the x position of the circle | None |
35
+ | y | int | the y position of the circle | None |
36
+ | radius | int | the radius of the circle | 10 |
37
+ | color | str | the color of the circle | "black" |
38
+ | outline | bool | whether the circle is just an outline | False |
39
+
40
+ Methods:
41
+ | Method | Purpose | Arguments | Returns |
42
+ | :--- | :---: | :---: | ---: |
43
+ | is_obj_over | determines if a point is in the circle | x: int, y:int | bool |
44
+ | get_area | returns the area of the circle | None | bool |
45
+
46
+ ### Polygon
47
+ Arguments:
48
+ | Argument | Type | Role | Default |
49
+ | :--- | :---: | :---: | ---: |
50
+ | points | list[tuple] | the vertices of the polygon | None |
51
+ | color | str | the color of the polygon | "black" |
52
+ | outline | bool | whther the polygon is just an outline | False |
53
+
54
+ Methods:
55
+ | Method | Purpose | Arguments | Returns |
56
+ | :--- | :---: | :---: | ---: |
57
+ | is_obj_over | determines if a point is in the polygon | x: int, y:int | bool |
58
+
59
+ ### Line
60
+ Arguments:
61
+ | Argument | Type | Role | Default |
62
+ | :--- | :---: | :---: | ---: |
63
+ | points | list[tuple] |the points that the line will connect | None |
64
+ | width | int | the width of the line | 10 |
65
+ | color | str | the color of the line | "black" |
66
+
67
+ Methods:
68
+ | Method | Purpose | Arguments | Returns |
69
+ | :--- | :---: | :---: | ---: |
70
+ | is_obj_over | determines if a point is over the line | x: int, y:int | bool |
71
+
72
+ ### Text
73
+ Arguments:
74
+ | Argument | Type | Role | Default |
75
+ | :--- | :---: | :---: | ---: |
76
+ | x | int | the x value of the text | None |
77
+ | y | int | the y value of the text | None |
78
+ | text | str | the text of the text | None |
79
+ | color | str | the color of the text | "black" |
80
+ | size | int | the size of the text | 36 |
81
+
82
+ Methods:
83
+ | Method | Purpose | Arguments | Returns |
84
+ | :--- | :---: | :---: | ---: |
85
+ | is_obj_over | determines if a point is in the text | x: int, y:int | bool |
86
+
87
+ ---
88
+
89
+ ## Run
90
+ `run()` is Simple Graphics main function.
91
+ it creates a screen where all defined objects appear and can be interacted with.
92
+
93
+ Arguments:
94
+ | Argument | Type | Role | Default |
95
+ | :--- | :---: | :---: | ---: |
96
+ | width | int | The screen width in pixels | 200 |
97
+ | height | int | the screen height in pixels | 200 |
98
+ | resizable | bool | whether or not the screen is resizable via dragging | True |
99
+ | caption | str | the name of the graphics window | "SG window" |
100
+
101
+
102
+ ## Collisions
103
+
104
+ to check for a collision between 2 shapes, use the `is_colliding()` method:
105
+ ```python
106
+ rect1 = Rect(10, 10)
107
+ ball = Circle(10 , 5)
108
+ print(is_colliding(ball, rect1)) # True
109
+ ```
110
+ ---
111
+ ## Events
112
+
113
+ Instead of a for loop, Simple Graphics handles events using decorators
114
+
115
+ ### on_tick
116
+ The `on_tick` decorator runs the function every frame
117
+ The default framerate is 60 FPS
118
+ Functions with the `on_tick` decorator may not take arguments
119
+ ```python
120
+ @on_tick
121
+ def main():
122
+ do_something()
123
+
124
+ run()
125
+ ```
126
+
127
+ ### on_press
128
+
129
+ the `on_press` runs the decorator whenever a specified button is pressed, or when any button is pressed if none are specified
130
+
131
+ The function below will run whenever w or s is pressed
132
+ ```python
133
+ @on_press("w,s")
134
+ def do_w():
135
+ do_soemthing()
136
+
137
+ run()
138
+ ```
139
+
140
+ The function below will run whenever any key is pressed
141
+ ```python
142
+ @on_press
143
+ def any_key():
144
+ do_something()
145
+
146
+ run()
147
+ ```
148
+
149
+ Functions with the `@on_press` decorator may only take 1 argument, which will show up as the key pressed
150
+ ```python
151
+ @on_press
152
+ def print_key(key):
153
+ print(key)
154
+
155
+ run()
156
+ ```
157
+
158
+ ### on_hold
159
+
160
+ 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
161
+
162
+ The function below will run for as long as w or s is pressed
163
+ ```python
164
+ @on_hold("w,s")
165
+ def do_w():
166
+ do_soemthing()
167
+
168
+ run()
169
+ ```
170
+
171
+ The function below will run while any key is pressed
172
+ ```python
173
+ @on_press
174
+ def any_key():
175
+ do_something()
176
+
177
+ run()
178
+ ```
179
+
180
+ Functions with the `@on_hold` decorator may only take 1 argument, which will show up as the key held
181
+ ```python
182
+ @on_press
183
+ def print_key(key):
184
+ print(key)
185
+
186
+ run()
187
+ ```
188
+
189
+ ### on_click
190
+
191
+ 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
192
+
193
+ the function below will run whenever the button rect is clicked
194
+ ```python
195
+ button = Rect(10, 10)
196
+ @on_click(button)
197
+ def click_button():
198
+ print("button clicked")
199
+
200
+ run()
201
+ ```
202
+
203
+ the function below will run when there is any click at all
204
+
205
+ ```python
206
+ @on_click
207
+ def print_click():
208
+ print("clicked!")
209
+
210
+ run()
211
+ ```
212
+
213
+ ---
214
+ ## The mouse
215
+ A mouse object is already initialized in the Simple Graphics import
216
+
217
+ to get mouse coordinates, simply use `mouse.x` and `mouse.y`
218
+
219
+ Example:
220
+ ```python
221
+ @on_tick
222
+ def main():
223
+ print(mouse.x, mouse.y)
224
+
225
+ run()
226
+ ```
227
+ ---
228
+ ## Miscellaneous Functions
229
+
230
+ ### set_bg
231
+ `set_bg()` simply sets the background to the specified color
232
+
233
+ the default background color is white
234
+ ```python
235
+ set_bg("black")
236
+ ```
237
+ changes the background color to black
238
+
239
+ ### clear_screen
240
+ `clear_screen()` clears the screen.
241
+ objects defined before a screen clear can no longer be interacted with
@@ -0,0 +1,22 @@
1
+ [project]
2
+ name = "Max-Hillyer"
3
+ version = "0.0.4"
4
+ authors = [
5
+ { name="Max-Hillyer", email="maxhillyer7012@gmail.com" },
6
+ ]
7
+ dependencies = [
8
+ "pygame",
9
+ ]
10
+ description = "A simple graphics library for python learners"
11
+ readme = "README.md"
12
+ requires-python = ">=3.9"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ license = "MIT"
18
+ license-files = ["LICEN[CS]E*"]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/pypa/sampleproject"
22
+ Issues = "https://github.com/pypa/sampleproject/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: Max-Hillyer
3
+ Version: 0.0.4
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/pypa/sampleproject
8
+ Project-URL: Issues, https://github.com/pypa/sampleproject/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,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/Max_Hillyer.egg-info/PKG-INFO
5
+ src/Max_Hillyer.egg-info/SOURCES.txt
6
+ src/Max_Hillyer.egg-info/dependency_links.txt
7
+ src/Max_Hillyer.egg-info/requires.txt
8
+ src/Max_Hillyer.egg-info/top_level.txt
9
+ src/simple_graphics/__init__.py
10
+ src/simple_graphics/module.py
@@ -0,0 +1 @@
1
+ simple_graphics
@@ -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()