pygkit 1.0.1__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.
- pygkit/__init__.py +41 -0
- pygkit/audio/__init__.py +6 -0
- pygkit/audio/soundmanager.py +89 -0
- pygkit/protocols.py +29 -0
- pygkit/transitions/__init__.py +18 -0
- pygkit/transitions/base.py +97 -0
- pygkit/transitions/effects.py +156 -0
- pygkit/transitions/fades.py +196 -0
- pygkit/transitions/wipes.py +185 -0
- pygkit/ui/__init__.py +15 -0
- pygkit/ui/base.py +125 -0
- pygkit/ui/container.py +21 -0
- pygkit/ui/cooldown.py +107 -0
- pygkit/ui/progressbar.py +64 -0
- pygkit/utils/__init__.py +19 -0
- pygkit/utils/interpolation.py +45 -0
- pygkit/utils/text.py +147 -0
- pygkit/utils/timer.py +35 -0
- pygkit-1.0.1.dist-info/METADATA +719 -0
- pygkit-1.0.1.dist-info/RECORD +23 -0
- pygkit-1.0.1.dist-info/WHEEL +5 -0
- pygkit-1.0.1.dist-info/licenses/LICENSE +674 -0
- pygkit-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
from pygame import SRCALPHA, Surface, draw
|
|
5
|
+
|
|
6
|
+
from ..utils.interpolation import ease_in_out
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _WipeBase:
|
|
10
|
+
__slots__ = ("_easing", "_elapsed", "_duration", "_done", "_mask")
|
|
11
|
+
|
|
12
|
+
def __init__(self, easing: Callable[[float], float] = ease_in_out) -> None:
|
|
13
|
+
self._easing = easing
|
|
14
|
+
self._elapsed = 0.0
|
|
15
|
+
self._duration = 0.0
|
|
16
|
+
self._done = False
|
|
17
|
+
self._mask: Surface | None = None
|
|
18
|
+
|
|
19
|
+
def start(self, duration: float) -> None:
|
|
20
|
+
self._elapsed = 0.0
|
|
21
|
+
self._duration = duration
|
|
22
|
+
self._done = False
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def done(self) -> bool:
|
|
26
|
+
return self._done
|
|
27
|
+
|
|
28
|
+
def reset(self) -> None:
|
|
29
|
+
self._elapsed = 0.0
|
|
30
|
+
self._duration = 0.0
|
|
31
|
+
self._done = False
|
|
32
|
+
self._mask = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class IrisIn(_WipeBase):
|
|
36
|
+
"""
|
|
37
|
+
Circle closes in from the edges to the center, revealing black (or a color).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
color: tuple[int, int, int] = (0, 0, 0),
|
|
43
|
+
easing: Callable[[float], float] = ease_in_out,
|
|
44
|
+
) -> None:
|
|
45
|
+
super().__init__(easing)
|
|
46
|
+
self._color = color
|
|
47
|
+
|
|
48
|
+
def update(self, dt: float) -> None:
|
|
49
|
+
if self._done:
|
|
50
|
+
return
|
|
51
|
+
self._elapsed += dt
|
|
52
|
+
if self._elapsed >= self._duration:
|
|
53
|
+
self._elapsed = self._duration
|
|
54
|
+
self._done = True
|
|
55
|
+
|
|
56
|
+
def render(
|
|
57
|
+
self,
|
|
58
|
+
screen: Surface,
|
|
59
|
+
source: Surface | None = None,
|
|
60
|
+
target: Surface | None = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
if source is not None:
|
|
63
|
+
screen.blit(source, (0, 0))
|
|
64
|
+
|
|
65
|
+
w, h = screen.get_size()
|
|
66
|
+
max_radius = int(math.ceil(math.sqrt(w * w + h * h) / 2))
|
|
67
|
+
|
|
68
|
+
t = self._easing(self._elapsed / self._duration) if self._duration > 0 else 1.0
|
|
69
|
+
radius = int(max_radius * (1.0 - t))
|
|
70
|
+
|
|
71
|
+
if radius <= 0:
|
|
72
|
+
screen.fill(self._color)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
cx, cy = w // 2, h // 2
|
|
76
|
+
|
|
77
|
+
if self._mask is None or self._mask.get_size() != (w, h):
|
|
78
|
+
self._mask = Surface((w, h), SRCALPHA)
|
|
79
|
+
|
|
80
|
+
self._mask.fill(self._color)
|
|
81
|
+
draw.circle(self._mask, (0, 0, 0, 0), (cx, cy), radius)
|
|
82
|
+
screen.blit(self._mask, (0, 0))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class IrisOut(_WipeBase):
|
|
86
|
+
"""
|
|
87
|
+
Circle expands from the center outward, revealing the target.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
easing: Callable[[float], float] = ease_in_out,
|
|
93
|
+
) -> None:
|
|
94
|
+
super().__init__(easing)
|
|
95
|
+
|
|
96
|
+
def update(self, dt: float) -> None:
|
|
97
|
+
if self._done:
|
|
98
|
+
return
|
|
99
|
+
self._elapsed += dt
|
|
100
|
+
if self._elapsed >= self._duration:
|
|
101
|
+
self._elapsed = self._duration
|
|
102
|
+
self._done = True
|
|
103
|
+
|
|
104
|
+
def render(
|
|
105
|
+
self,
|
|
106
|
+
screen: Surface,
|
|
107
|
+
source: Surface | None = None,
|
|
108
|
+
target: Surface | None = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
w, h = screen.get_size()
|
|
111
|
+
max_radius = int(math.ceil(math.sqrt(w * w + h * h) / 2))
|
|
112
|
+
|
|
113
|
+
t = self._easing(self._elapsed / self._duration) if self._duration > 0 else 1.0
|
|
114
|
+
radius = int(max_radius * t)
|
|
115
|
+
cx, cy = w // 2, h // 2
|
|
116
|
+
|
|
117
|
+
if target is not None:
|
|
118
|
+
screen.blit(target, (0, 0))
|
|
119
|
+
|
|
120
|
+
if radius < max_radius:
|
|
121
|
+
if self._mask is None or self._mask.get_size() != (w, h):
|
|
122
|
+
self._mask = Surface((w, h), SRCALPHA)
|
|
123
|
+
|
|
124
|
+
self._mask.fill((0, 0, 0, 255))
|
|
125
|
+
draw.circle(self._mask, (0, 0, 0, 0), (cx, cy), radius)
|
|
126
|
+
screen.blit(self._mask, (0, 0))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Slide(_WipeBase):
|
|
130
|
+
"""
|
|
131
|
+
Source slides offscreen while target slides in from the opposite side.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
direction: str = "left",
|
|
137
|
+
) -> None:
|
|
138
|
+
super().__init__()
|
|
139
|
+
if direction not in ("left", "right", "up", "down"):
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"Invalid direction '{direction}'. Must be left, right, up, or down."
|
|
142
|
+
)
|
|
143
|
+
self._direction = direction
|
|
144
|
+
self._buffer: Surface | None = None
|
|
145
|
+
|
|
146
|
+
def update(self, dt: float) -> None:
|
|
147
|
+
if self._done:
|
|
148
|
+
return
|
|
149
|
+
self._elapsed += dt
|
|
150
|
+
if self._elapsed >= self._duration:
|
|
151
|
+
self._elapsed = self._duration
|
|
152
|
+
self._done = True
|
|
153
|
+
|
|
154
|
+
def render(
|
|
155
|
+
self,
|
|
156
|
+
screen: Surface,
|
|
157
|
+
source: Surface | None = None,
|
|
158
|
+
target: Surface | None = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
w, h = screen.get_size()
|
|
161
|
+
t = self._elapsed / self._duration if self._duration > 0 else 1.0
|
|
162
|
+
|
|
163
|
+
if self._direction == "left":
|
|
164
|
+
out_offset = (-w * t, 0)
|
|
165
|
+
in_offset = (w * (1.0 - t), 0)
|
|
166
|
+
elif self._direction == "right":
|
|
167
|
+
out_offset = (w * t, 0)
|
|
168
|
+
in_offset = (-w * (1.0 - t), 0)
|
|
169
|
+
elif self._direction == "up":
|
|
170
|
+
out_offset = (0, -h * t)
|
|
171
|
+
in_offset = (0, h * (1.0 - t))
|
|
172
|
+
else:
|
|
173
|
+
out_offset = (0, h * t)
|
|
174
|
+
in_offset = (0, -h * (1.0 - t))
|
|
175
|
+
|
|
176
|
+
if self._buffer is None or self._buffer.get_size() != (w, h):
|
|
177
|
+
self._buffer = Surface((w, h))
|
|
178
|
+
|
|
179
|
+
self._buffer.fill((0, 0, 0))
|
|
180
|
+
if target is not None:
|
|
181
|
+
self._buffer.blit(target, in_offset)
|
|
182
|
+
if source is not None:
|
|
183
|
+
self._buffer.blit(source, out_offset)
|
|
184
|
+
|
|
185
|
+
screen.blit(self._buffer, (0, 0))
|
pygkit/ui/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .base import UIBase, UIOptions, BoxModel, BoxModelResult, generate_box_model
|
|
2
|
+
from .container import Container
|
|
3
|
+
from .cooldown import CooldownOverlay
|
|
4
|
+
from .progressbar import ProgressBarUI
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"UIBase",
|
|
8
|
+
"UIOptions",
|
|
9
|
+
"BoxModel",
|
|
10
|
+
"BoxModelResult",
|
|
11
|
+
"generate_box_model",
|
|
12
|
+
"ProgressBarUI",
|
|
13
|
+
"CooldownOverlay",
|
|
14
|
+
"Container",
|
|
15
|
+
]
|
pygkit/ui/base.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from typing import Any, Callable, List, TypedDict
|
|
2
|
+
|
|
3
|
+
import pygame
|
|
4
|
+
from pygame import SRCALPHA, Surface
|
|
5
|
+
from pygame.typing import ColorLike
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BoxModel(TypedDict, total=False):
|
|
9
|
+
margin_x: int
|
|
10
|
+
margin_y: int
|
|
11
|
+
padding_x: int
|
|
12
|
+
padding_y: int
|
|
13
|
+
width: int
|
|
14
|
+
height: int
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BoxModelResult(TypedDict, total=True):
|
|
18
|
+
left: int
|
|
19
|
+
top: int
|
|
20
|
+
offset_x: int
|
|
21
|
+
offset_y: int
|
|
22
|
+
full_width: int
|
|
23
|
+
full_height: int
|
|
24
|
+
content_width: int
|
|
25
|
+
content_height: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UIOptions(BoxModel, total=False):
|
|
29
|
+
border_radius: int
|
|
30
|
+
border_color: ColorLike
|
|
31
|
+
background: ColorLike
|
|
32
|
+
fill_color: ColorLike
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def generate_box_model(model: BoxModel) -> BoxModelResult:
|
|
36
|
+
width = model.get("width", 0)
|
|
37
|
+
height = model.get("height", 0)
|
|
38
|
+
padding_x = model.get("padding_x", 0)
|
|
39
|
+
padding_y = model.get("padding_y", 0)
|
|
40
|
+
margin_x = model.get("margin_x", 0)
|
|
41
|
+
margin_y = model.get("margin_y", 0)
|
|
42
|
+
border_width = model.get("border_width", 0)
|
|
43
|
+
|
|
44
|
+
inner_x = 2 * (padding_x + border_width)
|
|
45
|
+
inner_y = 2 * (padding_y + border_width)
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
"left": padding_x + border_width,
|
|
49
|
+
"top": padding_y + border_width,
|
|
50
|
+
"offset_x": margin_x,
|
|
51
|
+
"offset_y": margin_y,
|
|
52
|
+
"full_width": width,
|
|
53
|
+
"full_height": height,
|
|
54
|
+
"content_width": width - inner_x,
|
|
55
|
+
"content_height": height - inner_y,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class UIBase:
|
|
60
|
+
def __init__(self, options: UIOptions) -> None:
|
|
61
|
+
self.colors: dict[str, ColorLike] = {
|
|
62
|
+
"bg": options.get("background", (0, 0, 0, 0))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
self.box_model = generate_box_model(options)
|
|
66
|
+
|
|
67
|
+
self.border: dict[str, Any] = {
|
|
68
|
+
"radius": options.get("border_radius", 0),
|
|
69
|
+
"width": options.get("border_width", 0),
|
|
70
|
+
"color": options.get("border_color", (0, 0, 0, 0)),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
local_size = self.box_model["full_width"], self.box_model["full_height"]
|
|
74
|
+
self.local_surface = Surface(local_size, SRCALPHA)
|
|
75
|
+
|
|
76
|
+
self.renderable_plugins: List[Callable[[Surface], Any]] = []
|
|
77
|
+
|
|
78
|
+
def add_plugin(self, cb: Callable[[Surface], Any]) -> None:
|
|
79
|
+
self.renderable_plugins.append(cb)
|
|
80
|
+
|
|
81
|
+
def draw_base(self) -> None:
|
|
82
|
+
local_surf = self.local_surface
|
|
83
|
+
local_surf.fill((0, 0, 0, 0))
|
|
84
|
+
|
|
85
|
+
bg_color = self.colors["bg"]
|
|
86
|
+
border_width = self.border["width"]
|
|
87
|
+
border_radius = self.border["radius"]
|
|
88
|
+
border_color = self.border["color"]
|
|
89
|
+
|
|
90
|
+
content_pos = self.box_model["left"], self.box_model["top"]
|
|
91
|
+
content_size = self.box_model["content_width"], self.box_model["content_height"]
|
|
92
|
+
surface_size = local_surf.get_size()
|
|
93
|
+
|
|
94
|
+
if border_width > 0:
|
|
95
|
+
pygame.draw.rect(
|
|
96
|
+
local_surf,
|
|
97
|
+
border_color,
|
|
98
|
+
(0, 0, *surface_size),
|
|
99
|
+
width=border_width,
|
|
100
|
+
border_radius=border_radius,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
pygame.draw.rect(
|
|
104
|
+
local_surf,
|
|
105
|
+
bg_color,
|
|
106
|
+
(*content_pos, *content_size),
|
|
107
|
+
border_radius=max(0, border_radius - border_width),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def size(self) -> tuple[int, int]:
|
|
112
|
+
return (self.box_model["full_width"], self.box_model["full_height"])
|
|
113
|
+
|
|
114
|
+
def render(self, screen: Surface, pos_offset: tuple[int, int] = (0, 0)) -> None:
|
|
115
|
+
self.draw_base()
|
|
116
|
+
for plugin in self.renderable_plugins:
|
|
117
|
+
plugin(self.local_surface)
|
|
118
|
+
pos = (
|
|
119
|
+
self.box_model["offset_x"] + pos_offset[0],
|
|
120
|
+
self.box_model["offset_y"] + pos_offset[1],
|
|
121
|
+
)
|
|
122
|
+
screen.blit(self.local_surface, pos)
|
|
123
|
+
|
|
124
|
+
def update(self) -> None:
|
|
125
|
+
pass
|
pygkit/ui/container.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
|
|
3
|
+
from pygame import Surface
|
|
4
|
+
|
|
5
|
+
from .base import UIBase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Container:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self.children: List[Tuple[UIBase, Tuple[int, int]]] = []
|
|
11
|
+
|
|
12
|
+
def add(self, child: UIBase, pos: Tuple[int, int] = (0, 0)) -> None:
|
|
13
|
+
self.children.append((child, pos))
|
|
14
|
+
|
|
15
|
+
def update(self) -> None:
|
|
16
|
+
for child, _ in self.children:
|
|
17
|
+
child.update()
|
|
18
|
+
|
|
19
|
+
def render(self, screen: Surface) -> None:
|
|
20
|
+
for child, offset in self.children:
|
|
21
|
+
child.render(screen, offset)
|
pygkit/ui/cooldown.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import List, Optional, Tuple, Unpack
|
|
3
|
+
|
|
4
|
+
import pygame
|
|
5
|
+
from pygame import SRCALPHA, Surface, draw, transform
|
|
6
|
+
|
|
7
|
+
from ..utils.timer import Timer
|
|
8
|
+
from .base import UIBase, UIOptions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CooldownOverlay(UIBase):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
timer: Timer,
|
|
15
|
+
size: int | float,
|
|
16
|
+
icon: Optional[Surface] = None,
|
|
17
|
+
overlay_color: tuple[int, int, int, int] = (0, 0, 0, 255),
|
|
18
|
+
disabled_color: tuple[int, int, int, int] = (50, 50, 55, 200),
|
|
19
|
+
**overrides: Unpack[UIOptions],
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__({"width": int(size), "height": int(size), **overrides})
|
|
22
|
+
|
|
23
|
+
self.timer = timer
|
|
24
|
+
self.overlay_color = overlay_color
|
|
25
|
+
self.disabled_color = disabled_color
|
|
26
|
+
self.disabled = False
|
|
27
|
+
|
|
28
|
+
content_w = self.box_model["content_width"]
|
|
29
|
+
content_h = self.box_model["content_height"]
|
|
30
|
+
|
|
31
|
+
r = min(content_w, content_h) / 2
|
|
32
|
+
self.radius = r
|
|
33
|
+
self.scalar: float = 1.0
|
|
34
|
+
b = overrides.get("border_radius", 0)
|
|
35
|
+
if b < r:
|
|
36
|
+
self.scalar = (math.sqrt(2 * (r - b) ** 2) + b) / r
|
|
37
|
+
|
|
38
|
+
self.overlay_parent = Surface((content_w, content_h), SRCALPHA)
|
|
39
|
+
self._disabled_surf: Optional[Surface] = None
|
|
40
|
+
self.add_plugin(self._draw_overlay)
|
|
41
|
+
self.add_plugin(self._draw_disabled_overlay)
|
|
42
|
+
if icon is not None:
|
|
43
|
+
scale = content_w / icon.width, content_h / icon.height
|
|
44
|
+
self.icon = transform.scale_by(icon, scale)
|
|
45
|
+
self.add_plugin(self._draw_icon)
|
|
46
|
+
|
|
47
|
+
def _draw_icon(self, surface: Surface) -> None:
|
|
48
|
+
icon: Optional[Surface] = getattr(self, "icon", None)
|
|
49
|
+
if icon is None:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
center_x = self.box_model["content_width"] // 2
|
|
53
|
+
center_y = self.box_model["content_height"] // 2
|
|
54
|
+
|
|
55
|
+
rect = icon.get_rect(
|
|
56
|
+
center=(
|
|
57
|
+
self.box_model["left"] + center_x,
|
|
58
|
+
self.box_model["top"] + center_y,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if self.disabled:
|
|
63
|
+
icon.set_alpha(80)
|
|
64
|
+
elif not self.timer.reached():
|
|
65
|
+
icon.set_alpha(150)
|
|
66
|
+
elif icon.get_alpha() != 255:
|
|
67
|
+
icon.set_alpha(255)
|
|
68
|
+
surface.blit(icon, rect.topleft)
|
|
69
|
+
|
|
70
|
+
def _draw_overlay(self, surface: Surface) -> None:
|
|
71
|
+
if self.disabled or self.timer.reached():
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
progress = 1.0 - self.timer.ratio()
|
|
75
|
+
|
|
76
|
+
center = (self.radius, self.radius)
|
|
77
|
+
points: List[Tuple[float, float]] = [center]
|
|
78
|
+
|
|
79
|
+
end_degrees = int(progress * 360)
|
|
80
|
+
|
|
81
|
+
for degree in range(-90, end_degrees - 90):
|
|
82
|
+
angle = math.radians(degree)
|
|
83
|
+
x = self.radius + self.radius * math.cos(angle) * self.scalar
|
|
84
|
+
y = self.radius + self.radius * math.sin(angle) * self.scalar
|
|
85
|
+
points.append((x, y))
|
|
86
|
+
|
|
87
|
+
op = self.overlay_parent
|
|
88
|
+
op.fill((0, 0, 0, 0))
|
|
89
|
+
|
|
90
|
+
if len(points) > 2:
|
|
91
|
+
draw.polygon(op, self.overlay_color, points)
|
|
92
|
+
|
|
93
|
+
pos = self.box_model["left"], self.box_model["top"]
|
|
94
|
+
surface.blit(op, pos)
|
|
95
|
+
|
|
96
|
+
def _draw_disabled_overlay(self, surface: Surface) -> None:
|
|
97
|
+
if not self.disabled:
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
w = self.box_model["content_width"]
|
|
101
|
+
h = self.box_model["content_height"]
|
|
102
|
+
if self._disabled_surf is None or self._disabled_surf.get_size() != (w, h):
|
|
103
|
+
self._disabled_surf = Surface((w, h), SRCALPHA)
|
|
104
|
+
|
|
105
|
+
self._disabled_surf.fill(self.disabled_color)
|
|
106
|
+
pos = self.box_model["left"], self.box_model["top"]
|
|
107
|
+
surface.blit(self._disabled_surf, pos)
|
pygkit/ui/progressbar.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Unpack
|
|
2
|
+
|
|
3
|
+
import pygame
|
|
4
|
+
from pygame import Surface
|
|
5
|
+
|
|
6
|
+
from ..utils.interpolation import SimpleInterpolation
|
|
7
|
+
from .base import UIBase, UIOptions
|
|
8
|
+
|
|
9
|
+
PROGRESSBAR_DEFAULTS: UIOptions = {
|
|
10
|
+
"width": 250,
|
|
11
|
+
"height": 24,
|
|
12
|
+
"border_width": 2,
|
|
13
|
+
"border_radius": 6,
|
|
14
|
+
"padding_x": 2,
|
|
15
|
+
"padding_y": 2,
|
|
16
|
+
"margin_x": 0,
|
|
17
|
+
"margin_y": 0,
|
|
18
|
+
"border_color": (40, 44, 52, 255),
|
|
19
|
+
"background": (33, 37, 43, 255),
|
|
20
|
+
"fill_color": (255, 255, 255, 255),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ProgressBarUI(UIBase):
|
|
25
|
+
def __init__(self, **overrides: Unpack[UIOptions]) -> None:
|
|
26
|
+
options: UIOptions = {**PROGRESSBAR_DEFAULTS, **overrides}
|
|
27
|
+
super().__init__(options)
|
|
28
|
+
self.colors["fill"] = options.get("fill_color", (255, 255, 255, 255))
|
|
29
|
+
self.interpolation = SimpleInterpolation(speed=0.05)
|
|
30
|
+
|
|
31
|
+
def set_progress(self, value: float) -> None:
|
|
32
|
+
self.interpolation.set(value)
|
|
33
|
+
|
|
34
|
+
def get_progress(self) -> float:
|
|
35
|
+
return self.interpolation.current
|
|
36
|
+
|
|
37
|
+
def update(self) -> None:
|
|
38
|
+
self.interpolation.update()
|
|
39
|
+
|
|
40
|
+
def render(self, screen: Surface, pos_offset: tuple[int, int] = (0, 0)) -> None:
|
|
41
|
+
self.draw_base()
|
|
42
|
+
|
|
43
|
+
intrp_current = self.interpolation.current
|
|
44
|
+
if intrp_current > 0:
|
|
45
|
+
fill_width = int(self.box_model["content_width"] * intrp_current)
|
|
46
|
+
inner_radius = max(0, self.border["radius"] - self.border["width"])
|
|
47
|
+
|
|
48
|
+
pygame.draw.rect(
|
|
49
|
+
self.local_surface,
|
|
50
|
+
self.colors["fill"],
|
|
51
|
+
(
|
|
52
|
+
self.box_model["left"],
|
|
53
|
+
self.box_model["top"],
|
|
54
|
+
fill_width,
|
|
55
|
+
self.box_model["content_height"],
|
|
56
|
+
),
|
|
57
|
+
border_radius=inner_radius,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
pos = (
|
|
61
|
+
self.box_model["offset_x"] + pos_offset[0],
|
|
62
|
+
self.box_model["offset_y"] + pos_offset[1],
|
|
63
|
+
)
|
|
64
|
+
screen.blit(self.local_surface, pos)
|
pygkit/utils/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .interpolation import SimpleInterpolation, ease_in, ease_in_out, ease_out, lerp, smoothstep
|
|
2
|
+
from .text import fit, load_font, outline, render_multiline, shadow, wrap
|
|
3
|
+
from .timer import Timer
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Timer",
|
|
7
|
+
"SimpleInterpolation",
|
|
8
|
+
"lerp",
|
|
9
|
+
"smoothstep",
|
|
10
|
+
"ease_in_out",
|
|
11
|
+
"ease_out",
|
|
12
|
+
"ease_in",
|
|
13
|
+
"load_font",
|
|
14
|
+
"outline",
|
|
15
|
+
"shadow",
|
|
16
|
+
"wrap",
|
|
17
|
+
"render_multiline",
|
|
18
|
+
"fit",
|
|
19
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def lerp(a: float, b: float, t: float) -> float:
|
|
5
|
+
return a + (b - a) * t
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def smoothstep(t: float) -> float:
|
|
9
|
+
return t * t * (3 - 2 * t)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ease_in(t: float) -> float:
|
|
13
|
+
return t * t
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ease_out(t: float) -> float:
|
|
17
|
+
return 1 - (1 - t) ** 2
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def ease_in_out(t: float) -> float:
|
|
21
|
+
if t < 0.5:
|
|
22
|
+
return 2 * t * t
|
|
23
|
+
return 1 - ((-2 * t + 2) ** 2) / 2
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SimpleInterpolation:
|
|
27
|
+
__slots__ = ("current", "target", "speed")
|
|
28
|
+
|
|
29
|
+
def __init__(self, value: float = 1.0, speed: float = 0.1) -> None:
|
|
30
|
+
self.current = value
|
|
31
|
+
self.target = value
|
|
32
|
+
self.speed = speed
|
|
33
|
+
|
|
34
|
+
def set(self, target: float) -> None:
|
|
35
|
+
self.target = max(0, min(target, 1.0))
|
|
36
|
+
|
|
37
|
+
def update(self) -> None:
|
|
38
|
+
diff = self.target - self.current
|
|
39
|
+
if abs(diff) < 0.001:
|
|
40
|
+
self.current = self.target
|
|
41
|
+
else:
|
|
42
|
+
self.current += diff * self.speed
|
|
43
|
+
|
|
44
|
+
def finished(self) -> bool:
|
|
45
|
+
return abs(self.current - self.target) < 0.001
|