pg-extended 0.0.1b1__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.
- pg_extended-0.0.1b1/LICENSE +21 -0
- pg_extended-0.0.1b1/PKG-INFO +65 -0
- pg_extended-0.0.1b1/README.md +50 -0
- pg_extended-0.0.1b1/pyproject.toml +29 -0
- pg_extended-0.0.1b1/setup.cfg +4 -0
- pg_extended-0.0.1b1/src/pg_extended/Core/Base/AnimatedValue.py +246 -0
- pg_extended-0.0.1b1/src/pg_extended/Core/Base/Callback.py +57 -0
- pg_extended-0.0.1b1/src/pg_extended/Core/Base/DynamicValue.py +100 -0
- pg_extended-0.0.1b1/src/pg_extended/Core/Base/__init__.py +3 -0
- pg_extended-0.0.1b1/src/pg_extended/Core/Composites/CircleArea.py +33 -0
- pg_extended-0.0.1b1/src/pg_extended/Core/Composites/RectArea.py +40 -0
- pg_extended-0.0.1b1/src/pg_extended/Core/Composites/__init__.py +2 -0
- pg_extended-0.0.1b1/src/pg_extended/Core/__init__.py +5 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/Elements/Entity.py +77 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/Elements/Level.py +110 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/Elements/Player.py +3 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/Elements/SpriteAnimation.py +21 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/Elements/TextureAtlas.py +101 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/Elements/__init__.py +9 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/Scene.py +69 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/ViewPort.py +61 -0
- pg_extended-0.0.1b1/src/pg_extended/Game/__init__.py +15 -0
- pg_extended-0.0.1b1/src/pg_extended/Types.py +8 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Compounds/List.py +50 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Compounds/__init__.py +3 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/CopyElement.py +123 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Elements/Button.py +114 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Elements/Circle.py +65 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Elements/Section.py +163 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Elements/Slider.py +267 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Elements/TextBox.py +89 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Elements/TextInput.py +261 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Elements/Toggle.py +109 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/Elements/__init__.py +20 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/System.py +170 -0
- pg_extended-0.0.1b1/src/pg_extended/UI/__init__.py +23 -0
- pg_extended-0.0.1b1/src/pg_extended/Util/ImgManipulation.py +97 -0
- pg_extended-0.0.1b1/src/pg_extended/Util/Misc.py +17 -0
- pg_extended-0.0.1b1/src/pg_extended/Util/__init__.py +2 -0
- pg_extended-0.0.1b1/src/pg_extended/Window/Core.py +34 -0
- pg_extended-0.0.1b1/src/pg_extended/Window/EventManager.py +38 -0
- pg_extended-0.0.1b1/src/pg_extended/Window/Lifecycle.py +42 -0
- pg_extended-0.0.1b1/src/pg_extended/Window/MainLoop.py +48 -0
- pg_extended-0.0.1b1/src/pg_extended/Window/SceneManager.py +52 -0
- pg_extended-0.0.1b1/src/pg_extended/Window/SystemManager.py +95 -0
- pg_extended-0.0.1b1/src/pg_extended/Window/Utility.py +50 -0
- pg_extended-0.0.1b1/src/pg_extended/Window/__init__.py +1 -0
- pg_extended-0.0.1b1/src/pg_extended/__init__.py +16 -0
- pg_extended-0.0.1b1/src/pg_extended.egg-info/PKG-INFO +65 -0
- pg_extended-0.0.1b1/src/pg_extended.egg-info/SOURCES.txt +51 -0
- pg_extended-0.0.1b1/src/pg_extended.egg-info/dependency_links.txt +1 -0
- pg_extended-0.0.1b1/src/pg_extended.egg-info/requires.txt +3 -0
- pg_extended-0.0.1b1/src/pg_extended.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Saurabh
|
|
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,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pg-extended
|
|
3
|
+
Version: 0.0.1b1
|
|
4
|
+
Summary: A lightweight 2d game engine built on top of pygame.
|
|
5
|
+
Author-email: Saurabh Jadhav <pgextended.2025@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Saurabh262004/pg-extended
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: pygame-ce
|
|
12
|
+
Requires-Dist: pyperclip
|
|
13
|
+
Requires-Dist: easygui
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# pg-extended
|
|
17
|
+
*A lightweight UI wrapper and window manager for pygame.*
|
|
18
|
+
|
|
19
|
+
> [!WARNING]
|
|
20
|
+
> - This library is in the early stages of development and may have many breaking changes in the future.
|
|
21
|
+
> - Some of the features are still to be refined and added.
|
|
22
|
+
|
|
23
|
+
## Goal
|
|
24
|
+
- Provide a dynamic, customizable, and intuitive system for building UI and game elements in pygame.
|
|
25
|
+
- Handle repetitive UI / window management tasks for the user.
|
|
26
|
+
- Eventually evolve into a small, modular game engine.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
> [!IMPORTANT]
|
|
32
|
+
> pip install support will be added later on.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
git clone https://github.com/Saurabh262004/pg-extended.git
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Copy the `pg_extended/` folder into the root directory of your project.
|
|
39
|
+
Make sure that the folder name remains exactly `pg_extended/`.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# Example
|
|
44
|
+
|
|
45
|
+
A simple example of how to initialize an empty window with pg_extended.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from pg_extended import Window
|
|
49
|
+
|
|
50
|
+
# create a window with "Demo pgx window" as the title with 1280x720 resolution
|
|
51
|
+
app = Window("Demo pgx window", (1280, 720))
|
|
52
|
+
|
|
53
|
+
# all the UI / Game setup can be done here
|
|
54
|
+
|
|
55
|
+
# open the window
|
|
56
|
+
app.openWindow()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> For more details, please checkout the [wiki page](../../wiki).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## [HVision](https://github.com/Saurabh262004/HVision)
|
|
64
|
+
|
|
65
|
+
### A large project by me that uses pg_extended in a real environment.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# pg-extended
|
|
2
|
+
*A lightweight UI wrapper and window manager for pygame.*
|
|
3
|
+
|
|
4
|
+
> [!WARNING]
|
|
5
|
+
> - This library is in the early stages of development and may have many breaking changes in the future.
|
|
6
|
+
> - Some of the features are still to be refined and added.
|
|
7
|
+
|
|
8
|
+
## Goal
|
|
9
|
+
- Provide a dynamic, customizable, and intuitive system for building UI and game elements in pygame.
|
|
10
|
+
- Handle repetitive UI / window management tasks for the user.
|
|
11
|
+
- Eventually evolve into a small, modular game engine.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
> [!IMPORTANT]
|
|
17
|
+
> pip install support will be added later on.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
git clone https://github.com/Saurabh262004/pg-extended.git
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Copy the `pg_extended/` folder into the root directory of your project.
|
|
24
|
+
Make sure that the folder name remains exactly `pg_extended/`.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# Example
|
|
29
|
+
|
|
30
|
+
A simple example of how to initialize an empty window with pg_extended.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from pg_extended import Window
|
|
34
|
+
|
|
35
|
+
# create a window with "Demo pgx window" as the title with 1280x720 resolution
|
|
36
|
+
app = Window("Demo pgx window", (1280, 720))
|
|
37
|
+
|
|
38
|
+
# all the UI / Game setup can be done here
|
|
39
|
+
|
|
40
|
+
# open the window
|
|
41
|
+
app.openWindow()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> For more details, please checkout the [wiki page](../../wiki).
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## [HVision](https://github.com/Saurabh262004/HVision)
|
|
49
|
+
|
|
50
|
+
### A large project by me that uses pg_extended in a real environment.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pg-extended"
|
|
7
|
+
version = "0.0.1b1"
|
|
8
|
+
description = "A lightweight 2d game engine built on top of pygame."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Saurabh Jadhav", email = "pgextended.2025@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
dependencies = [
|
|
17
|
+
"pygame-ce",
|
|
18
|
+
"pyperclip",
|
|
19
|
+
"easygui"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools]
|
|
23
|
+
package-dir = {"" = "src"}
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/Saurabh262004/pg-extended"
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from copy import copy
|
|
3
|
+
from pg_extended.Core.Base.DynamicValue import DynamicValue
|
|
4
|
+
|
|
5
|
+
INTERPOLATION_TYPES = ['linear', 'easeIn', 'easeOut', 'easeInOut', 'custom']
|
|
6
|
+
DEFAULT_POS_VALS = ['start', 'end']
|
|
7
|
+
|
|
8
|
+
type valuesType = list[DynamicValue | AnimatedValue | int | float] | list[DynamicValue | AnimatedValue | int | float]
|
|
9
|
+
|
|
10
|
+
class AnimatedValue:
|
|
11
|
+
def __init__(self, values: valuesType, duration: float, defaultPos: str = 'start', interpolation: str = 'linear', callback: callable = None, customInterpolation: callable = None):
|
|
12
|
+
if len(values) < 2:
|
|
13
|
+
raise ValueError("Animator requires a minimum of two values to animate between.")
|
|
14
|
+
|
|
15
|
+
if not interpolation in INTERPOLATION_TYPES:
|
|
16
|
+
raise ValueError(f'Invalid interpolation type: {interpolation}. Must be one of: {INTERPOLATION_TYPES}')
|
|
17
|
+
|
|
18
|
+
if interpolation == 'custom' and customInterpolation is None:
|
|
19
|
+
raise ValueError('Custom interpolation function must be provided when using "custom" interpolation type.')
|
|
20
|
+
|
|
21
|
+
if not defaultPos in DEFAULT_POS_VALS:
|
|
22
|
+
raise ValueError(f'Invalid defaultPos: {defaultPos}. Must be one of: {DEFAULT_POS_VALS}')
|
|
23
|
+
|
|
24
|
+
self.values = values
|
|
25
|
+
self.rawValues: list[int | float] = []
|
|
26
|
+
self.duration = duration
|
|
27
|
+
self.interpolation = interpolation
|
|
28
|
+
self.callback = callback
|
|
29
|
+
self.defaultPos = defaultPos
|
|
30
|
+
|
|
31
|
+
self.updateValues()
|
|
32
|
+
|
|
33
|
+
if self.defaultPos == 'start':
|
|
34
|
+
self.value = self.rawValues[0]
|
|
35
|
+
else:
|
|
36
|
+
self.value = self.rawValues[-1]
|
|
37
|
+
|
|
38
|
+
self.animStart: float = None
|
|
39
|
+
self.reverse: bool = False
|
|
40
|
+
self.repeats: int = 0
|
|
41
|
+
self.alternate: bool = False
|
|
42
|
+
self.hasPlayedOnce: bool = False
|
|
43
|
+
self.delay: int = 0
|
|
44
|
+
|
|
45
|
+
if self.interpolation == 'linear':
|
|
46
|
+
self.interpolationStep = self.linear
|
|
47
|
+
elif self.interpolation == 'easeIn':
|
|
48
|
+
self.interpolationStep = self.easeIn
|
|
49
|
+
elif self.interpolation == 'easeOut':
|
|
50
|
+
self.interpolationStep = self.easeOut
|
|
51
|
+
elif self.interpolation == 'easeInOut':
|
|
52
|
+
self.interpolationStep = self.easeInOut
|
|
53
|
+
elif self.interpolation == 'custom':
|
|
54
|
+
self.interpolationStep = customInterpolation
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def linear(start: float, end: float, t: float) -> float:
|
|
58
|
+
if t <= 0:
|
|
59
|
+
return start
|
|
60
|
+
elif t >= 1:
|
|
61
|
+
return end
|
|
62
|
+
|
|
63
|
+
return start + (end - start) * t
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def easeIn(start: float, end: float, t: float) -> float:
|
|
67
|
+
if t <= 0:
|
|
68
|
+
return start
|
|
69
|
+
elif t >= 1:
|
|
70
|
+
return end
|
|
71
|
+
|
|
72
|
+
t = t ** 2
|
|
73
|
+
|
|
74
|
+
return start + (end - start) * t
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def easeOut(start: float, end: float, t: float) -> float:
|
|
78
|
+
if t <= 0:
|
|
79
|
+
return start
|
|
80
|
+
elif t >= 1:
|
|
81
|
+
return end
|
|
82
|
+
|
|
83
|
+
t = 1 - (1 - t) ** 2
|
|
84
|
+
|
|
85
|
+
return start + (end - start) * t
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def easeInOut(start: float, end: float, t: float) -> float:
|
|
89
|
+
if t <= 0:
|
|
90
|
+
return start
|
|
91
|
+
elif t >= 1:
|
|
92
|
+
return end
|
|
93
|
+
|
|
94
|
+
t = t**3 * (t * (t * 6 - 15) + 10)
|
|
95
|
+
|
|
96
|
+
return start + (end - start) * t
|
|
97
|
+
|
|
98
|
+
# get raw values from animated / dynamic values
|
|
99
|
+
def updateValues(self):
|
|
100
|
+
self.rawValues = []
|
|
101
|
+
|
|
102
|
+
for value in self.values:
|
|
103
|
+
if isinstance(value, (DynamicValue, AnimatedValue)):
|
|
104
|
+
value.resolveValue()
|
|
105
|
+
self.rawValues.append(value.value)
|
|
106
|
+
else:
|
|
107
|
+
self.rawValues.append(value)
|
|
108
|
+
|
|
109
|
+
# get an interpolated value from normalized t
|
|
110
|
+
def interpolate(self, t: float):
|
|
111
|
+
if t <= 0:
|
|
112
|
+
return self.rawValues[0]
|
|
113
|
+
elif t >= 1:
|
|
114
|
+
return self.rawValues[-1]
|
|
115
|
+
|
|
116
|
+
processingVals = copy(self.rawValues)
|
|
117
|
+
|
|
118
|
+
while len(processingVals) > 1:
|
|
119
|
+
tmp = []
|
|
120
|
+
|
|
121
|
+
for i in range(len(processingVals) - 1):
|
|
122
|
+
tmp.append(
|
|
123
|
+
self.interpolationStep(processingVals[i], processingVals[i + 1], t)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
processingVals = tmp
|
|
127
|
+
|
|
128
|
+
self.value = processingVals[0]
|
|
129
|
+
|
|
130
|
+
# calculate current animation time, get normalized t, call .interpolate() etc..
|
|
131
|
+
# most importantly this is the function you need to call to update the animated value
|
|
132
|
+
def resolveValue(self):
|
|
133
|
+
if self.animStart is None:
|
|
134
|
+
self.updateRestingPos()
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
elapsedTime = ((time.perf_counter() * 1000) - self.animStart) - self.delay
|
|
138
|
+
|
|
139
|
+
self.updateValues()
|
|
140
|
+
|
|
141
|
+
if elapsedTime >= self.duration:
|
|
142
|
+
self.finishAnim()
|
|
143
|
+
else:
|
|
144
|
+
if self.reverse:
|
|
145
|
+
t = 1 - (elapsedTime / self.duration)
|
|
146
|
+
else:
|
|
147
|
+
t = elapsedTime / self.duration
|
|
148
|
+
|
|
149
|
+
self.interpolate(t)
|
|
150
|
+
|
|
151
|
+
# handle animation ends, repeats, callbacks used by .resolveValue()
|
|
152
|
+
def finishAnim(self):
|
|
153
|
+
if self.reverse:
|
|
154
|
+
self.value = self.rawValues[0]
|
|
155
|
+
else:
|
|
156
|
+
self.value = self.rawValues[-1]
|
|
157
|
+
|
|
158
|
+
self.animStart = None
|
|
159
|
+
|
|
160
|
+
self.hasPlayedOnce = True
|
|
161
|
+
|
|
162
|
+
if self.repeats > 0:
|
|
163
|
+
self.repeats -= 1
|
|
164
|
+
self.trigger(self.reverse, self.repeats, self.alternate)
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
if self.repeats == -1:
|
|
168
|
+
self.trigger(self.reverse, self.repeats, self.alternate)
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
if self.callback is not None:
|
|
172
|
+
self.callback()
|
|
173
|
+
|
|
174
|
+
# updates the value to a default idle position when no animation is playing
|
|
175
|
+
def updateRestingPos(self):
|
|
176
|
+
self.updateValues()
|
|
177
|
+
|
|
178
|
+
A = self.hasPlayedOnce
|
|
179
|
+
B = self.defaultPos == 'start'
|
|
180
|
+
C = self.reverse
|
|
181
|
+
|
|
182
|
+
'''
|
|
183
|
+
this let's the system decide the resting position with only the default position when it hasn't ran yet
|
|
184
|
+
once it has ran, the reverse value also has an effect on the resting value
|
|
185
|
+
|
|
186
|
+
if self.hasPlayedOnce:
|
|
187
|
+
if self.defaultPos == 'start':
|
|
188
|
+
if self.reverse:
|
|
189
|
+
self.value = self.values[0].value
|
|
190
|
+
else:
|
|
191
|
+
self.value = self.values[-1].value
|
|
192
|
+
else:
|
|
193
|
+
if self.reverse:
|
|
194
|
+
self.value = self.values[-1].value
|
|
195
|
+
else:
|
|
196
|
+
self.value = self.values[0].value
|
|
197
|
+
else:
|
|
198
|
+
if self.defaultPos == 'start':
|
|
199
|
+
self.value = self.values[0].value
|
|
200
|
+
else:
|
|
201
|
+
self.value = self.values[-1].value
|
|
202
|
+
|
|
203
|
+
condition for choosing first value:
|
|
204
|
+
(A and B and C) or (A and not B and not C) or (not A and B)
|
|
205
|
+
condition for choosing last value:
|
|
206
|
+
(A and B and not C) or (A and not B and C) or (not A and not B)
|
|
207
|
+
|
|
208
|
+
compacted:
|
|
209
|
+
(A and (B == C)) or (not A and B)
|
|
210
|
+
|
|
211
|
+
maximum performance version:
|
|
212
|
+
(B and not A) or (A and (B == C))
|
|
213
|
+
'''
|
|
214
|
+
|
|
215
|
+
pickStart = (B and not A) or (A and (B == C))
|
|
216
|
+
|
|
217
|
+
self.value = self.rawValues[0] if pickStart else self.rawValues[-1]
|
|
218
|
+
|
|
219
|
+
# triggers animation
|
|
220
|
+
def trigger(self, reverse: bool = False, repeats: int = 0, alternate: bool = False, delay: int = 0):
|
|
221
|
+
self.animStart = time.perf_counter() * 1000
|
|
222
|
+
|
|
223
|
+
self.repeats = repeats
|
|
224
|
+
self.alternate = alternate
|
|
225
|
+
self.delay = delay
|
|
226
|
+
|
|
227
|
+
if self.hasPlayedOnce:
|
|
228
|
+
if self.alternate:
|
|
229
|
+
self.reverse = not self.reverse
|
|
230
|
+
else:
|
|
231
|
+
self.reverse = reverse
|
|
232
|
+
else:
|
|
233
|
+
self.reverse = reverse
|
|
234
|
+
|
|
235
|
+
# stops all animations, resets repeats and instantly snaps the value to a resting position
|
|
236
|
+
def terminate(self):
|
|
237
|
+
if self.animStart is None:
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
self.animStart = None
|
|
241
|
+
self.repeats = 0
|
|
242
|
+
|
|
243
|
+
if self.reverse:
|
|
244
|
+
self.value = self.rawValues[0]
|
|
245
|
+
else:
|
|
246
|
+
self.value = self.rawValues[-1]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from pg_extended.Core.Base import DynamicValue, AnimatedValue
|
|
3
|
+
from pg_extended.Types import CallableLike
|
|
4
|
+
|
|
5
|
+
class Callback:
|
|
6
|
+
def __init__(self, triggers: list[str] | tuple[str], func: CallableLike, staticArgs: dict[str, Any] = None, extraArgKeys: dict[str, str] = None):
|
|
7
|
+
self.triggers = triggers
|
|
8
|
+
self.func = func
|
|
9
|
+
self.staticArgs = staticArgs or {}
|
|
10
|
+
self.resolvedArgs = {}
|
|
11
|
+
self.extraArgKeys = extraArgKeys or {}
|
|
12
|
+
self.totalArgs = {}
|
|
13
|
+
|
|
14
|
+
def _setExtraArgs(self, args: dict[str, Any] = None):
|
|
15
|
+
args = args or {}
|
|
16
|
+
|
|
17
|
+
self.totalArgs = self.staticArgs.copy()
|
|
18
|
+
|
|
19
|
+
for key, value in args.items():
|
|
20
|
+
if key in self.extraArgKeys:
|
|
21
|
+
self.totalArgs[self.extraArgKeys[key]] = value
|
|
22
|
+
|
|
23
|
+
def resolveArgs(self):
|
|
24
|
+
self.resolvedArgs = {}
|
|
25
|
+
|
|
26
|
+
for key in self.totalArgs:
|
|
27
|
+
val = self.totalArgs[key]
|
|
28
|
+
|
|
29
|
+
if isinstance(val, (DynamicValue, AnimatedValue)):
|
|
30
|
+
val.resolveValue()
|
|
31
|
+
self.resolvedArgs[key] = val.value
|
|
32
|
+
else:
|
|
33
|
+
self.resolvedArgs[key] = val
|
|
34
|
+
|
|
35
|
+
def call(self, extraArgs: dict[str, Any] = None):
|
|
36
|
+
extraArgs = extraArgs or {}
|
|
37
|
+
|
|
38
|
+
self._setExtraArgs(extraArgs)
|
|
39
|
+
|
|
40
|
+
self.resolveArgs()
|
|
41
|
+
|
|
42
|
+
self.func(**self.resolvedArgs)
|
|
43
|
+
|
|
44
|
+
class CallbackSet:
|
|
45
|
+
def __init__(self, callbacks: list[Callback] | tuple[Callback]):
|
|
46
|
+
self.callbacks = callbacks
|
|
47
|
+
self.callbacksDict = {}
|
|
48
|
+
|
|
49
|
+
for callback in self.callbacks:
|
|
50
|
+
for tgr in callback.triggers:
|
|
51
|
+
self.callbacksDict.setdefault(tgr, []).append(callback)
|
|
52
|
+
|
|
53
|
+
def call(self, trigger: str, extraArgs: dict[str, Any] = None):
|
|
54
|
+
if trigger not in self.callbacksDict: return None
|
|
55
|
+
|
|
56
|
+
for callback in self.callbacksDict[trigger]:
|
|
57
|
+
callback.call(extraArgs)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any
|
|
3
|
+
from pg_extended.Types import CallableLike
|
|
4
|
+
import types
|
|
5
|
+
|
|
6
|
+
class DynamicValue:
|
|
7
|
+
def __init__(self, ref: Any, lookup: str | None = None, args: dict[str, Any] | None = None, percent: int | float | None = None):
|
|
8
|
+
self.reference = ref
|
|
9
|
+
self.lookup = lookup
|
|
10
|
+
self.args = args
|
|
11
|
+
self.percent = percent
|
|
12
|
+
self.value: Any = None
|
|
13
|
+
self.resolveValue: CallableLike = None
|
|
14
|
+
|
|
15
|
+
self.assignResolveMethod()
|
|
16
|
+
|
|
17
|
+
self.resolveValue()
|
|
18
|
+
|
|
19
|
+
def _IFPer(self):
|
|
20
|
+
self.value = self.reference / 100 * self.percent
|
|
21
|
+
|
|
22
|
+
def _dictLookup(self):
|
|
23
|
+
self.value = self.reference[self.lookup]
|
|
24
|
+
|
|
25
|
+
def _dictLookupPer(self):
|
|
26
|
+
self.value = self.reference[self.lookup] / 100 * self.percent
|
|
27
|
+
|
|
28
|
+
def _CV(self):
|
|
29
|
+
self.reference.resolveValue()
|
|
30
|
+
self.value = self.reference.value
|
|
31
|
+
|
|
32
|
+
def _CVPer(self):
|
|
33
|
+
self.reference.resolveValue()
|
|
34
|
+
self.value = self.reference.value / 100 * self.percent
|
|
35
|
+
|
|
36
|
+
def _call(self):
|
|
37
|
+
self.value = self.reference()
|
|
38
|
+
|
|
39
|
+
def _callPer(self):
|
|
40
|
+
self.value = self.reference() / 100 * self.percent
|
|
41
|
+
|
|
42
|
+
def _callArgs(self):
|
|
43
|
+
self.value = self.reference(**self.args)
|
|
44
|
+
|
|
45
|
+
def _callArgsPer(self):
|
|
46
|
+
self.value = self.reference(**self.args) / 100 * self.percent
|
|
47
|
+
|
|
48
|
+
def _objLookup(self):
|
|
49
|
+
self.value = getattr(self.reference, self.lookup)
|
|
50
|
+
|
|
51
|
+
def _objLookupPer(self):
|
|
52
|
+
self.value = getattr(self.reference, self.lookup) / 100 * self.percent
|
|
53
|
+
|
|
54
|
+
def _direct(self):
|
|
55
|
+
self.value = self.reference
|
|
56
|
+
|
|
57
|
+
def assignResolveMethod(self):
|
|
58
|
+
from pg_extended.Core.Base.AnimatedValue import AnimatedValue
|
|
59
|
+
|
|
60
|
+
# numbers with a percent value given
|
|
61
|
+
if isinstance(self.reference, (int, float)) and self.percent is not None:
|
|
62
|
+
self.resolveValue = self._IFPer
|
|
63
|
+
|
|
64
|
+
# dicts
|
|
65
|
+
elif isinstance(self.reference, dict) and self.lookup is not None:
|
|
66
|
+
if self.percent is None:
|
|
67
|
+
self.resolveValue = self._dictLookup
|
|
68
|
+
else:
|
|
69
|
+
self.resolveValue = self._dictLookupPer
|
|
70
|
+
|
|
71
|
+
# DV or AV
|
|
72
|
+
elif isinstance(self.reference, DynamicValue) or isinstance(self.reference, AnimatedValue):
|
|
73
|
+
if self.percent is None:
|
|
74
|
+
self.resolveValue = self._CV
|
|
75
|
+
else:
|
|
76
|
+
self.resolveValue = self._CVPer
|
|
77
|
+
|
|
78
|
+
# callable
|
|
79
|
+
elif isinstance(self.reference, (types.FunctionType | types.BuiltinFunctionType | types.MethodType)):
|
|
80
|
+
if self.args is None:
|
|
81
|
+
if self.percent is None:
|
|
82
|
+
self.resolveValue = self._call
|
|
83
|
+
else:
|
|
84
|
+
self.resolveValue = self._callPer
|
|
85
|
+
else:
|
|
86
|
+
if self.percent is None:
|
|
87
|
+
self.resolveValue = self._callArgs
|
|
88
|
+
else:
|
|
89
|
+
self.resolveValue = self._callArgsPer
|
|
90
|
+
|
|
91
|
+
# none of the above and lookup is provided, assume it's a class + attribute
|
|
92
|
+
elif isinstance(self.lookup, str):
|
|
93
|
+
if self.percent is None:
|
|
94
|
+
self.resolveValue = self._objLookup
|
|
95
|
+
else:
|
|
96
|
+
self.resolveValue = self._objLookupPer
|
|
97
|
+
|
|
98
|
+
# dump everything else into direct
|
|
99
|
+
else:
|
|
100
|
+
self.resolveValue = self._direct
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pg_extended.Core.Base.DynamicValue import DynamicValue
|
|
2
|
+
from pg_extended.Core.Base.AnimatedValue import AnimatedValue
|
|
3
|
+
|
|
4
|
+
type NumValue = DynamicValue | AnimatedValue | int | float
|
|
5
|
+
|
|
6
|
+
class CircleArea:
|
|
7
|
+
def __init__(self, dimensions: dict[str, NumValue]):
|
|
8
|
+
self.dimensions = dimensions
|
|
9
|
+
|
|
10
|
+
if not len(self.dimensions) == 3:
|
|
11
|
+
raise ValueError(f'dimensions must contain 3 Dimension objects, received: {len(self.dimensions)}')
|
|
12
|
+
|
|
13
|
+
for key in ('x', 'y', 'radius'):
|
|
14
|
+
if not key in self.dimensions:
|
|
15
|
+
raise ValueError('dimensions must contain all of the following keys: \'x\', \'y\', \'radius\'')
|
|
16
|
+
|
|
17
|
+
self.x: int | float
|
|
18
|
+
self.y: int | float
|
|
19
|
+
self.radius: int | float
|
|
20
|
+
|
|
21
|
+
self.update()
|
|
22
|
+
|
|
23
|
+
def getDimValue(self, key: str) -> int | float:
|
|
24
|
+
return self.dimensions[key].value if isinstance(self.dimensions[key], (DynamicValue, AnimatedValue)) else self.dimensions[key]
|
|
25
|
+
|
|
26
|
+
def update(self):
|
|
27
|
+
for key in self.dimensions:
|
|
28
|
+
if isinstance(self.dimensions[key], (DynamicValue, AnimatedValue)):
|
|
29
|
+
self.dimensions[key].resolveValue()
|
|
30
|
+
|
|
31
|
+
self.x = self.getDimValue("x")
|
|
32
|
+
self.y = self.getDimValue("y")
|
|
33
|
+
self.radius = self.getDimValue("radius")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import pygame as pg
|
|
2
|
+
from pg_extended.Core.Base.DynamicValue import DynamicValue
|
|
3
|
+
from pg_extended.Core.Base.AnimatedValue import AnimatedValue
|
|
4
|
+
|
|
5
|
+
type NumValue = DynamicValue | AnimatedValue | int | float
|
|
6
|
+
|
|
7
|
+
class RectArea:
|
|
8
|
+
def __init__(self, dimensions: dict[str, NumValue]):
|
|
9
|
+
self.dimensions: dict[str, NumValue] = dimensions
|
|
10
|
+
|
|
11
|
+
if not len(self.dimensions) == 4:
|
|
12
|
+
raise ValueError(f'dimensions must contain 4 Dimension objects, received: {len(self.dimensions)}')
|
|
13
|
+
|
|
14
|
+
for key in ('x', 'y', 'width', 'height'):
|
|
15
|
+
if not key in self.dimensions:
|
|
16
|
+
raise ValueError('dimensions must contain all of the following keys: \'x\', \'y\', \'width\' \'height\'')
|
|
17
|
+
|
|
18
|
+
self.x: int | float
|
|
19
|
+
self.y: int | float
|
|
20
|
+
self.width: int | float
|
|
21
|
+
self.height: int | float
|
|
22
|
+
|
|
23
|
+
self.rect: pg.Rect = pg.Rect(0, 0, 0, 0)
|
|
24
|
+
|
|
25
|
+
self.update()
|
|
26
|
+
|
|
27
|
+
def getDimValue(self, key: str) -> int | float:
|
|
28
|
+
return self.dimensions[key].value if isinstance(self.dimensions[key], (DynamicValue, AnimatedValue)) else self.dimensions[key]
|
|
29
|
+
|
|
30
|
+
def update(self):
|
|
31
|
+
for key in self.dimensions:
|
|
32
|
+
if isinstance(self.dimensions[key], (DynamicValue, AnimatedValue)):
|
|
33
|
+
self.dimensions[key].resolveValue()
|
|
34
|
+
|
|
35
|
+
self.x = self.getDimValue("x")
|
|
36
|
+
self.y = self.getDimValue("y")
|
|
37
|
+
self.width = self.getDimValue("width")
|
|
38
|
+
self.height = self.getDimValue("height")
|
|
39
|
+
|
|
40
|
+
self.rect.update(self.x, self.y, self.width, self.height)
|