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
pygkit/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from .audio import *
|
|
2
|
+
from .transitions import *
|
|
3
|
+
from .ui import *
|
|
4
|
+
from .utils import *
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"SoundManager",
|
|
8
|
+
"SFX_CHANNELS",
|
|
9
|
+
"TransitionRunner",
|
|
10
|
+
"TransitionState",
|
|
11
|
+
"FadeToBlack",
|
|
12
|
+
"FadeFromBlack",
|
|
13
|
+
"Crossfade",
|
|
14
|
+
"Flash",
|
|
15
|
+
"IrisIn",
|
|
16
|
+
"IrisOut",
|
|
17
|
+
"Slide",
|
|
18
|
+
"PixelDissolve",
|
|
19
|
+
"Shake",
|
|
20
|
+
"Container",
|
|
21
|
+
"CooldownOverlay",
|
|
22
|
+
"ProgressBarUI",
|
|
23
|
+
"UIBase",
|
|
24
|
+
"UIOptions",
|
|
25
|
+
"BoxModel",
|
|
26
|
+
"BoxModelResult",
|
|
27
|
+
"generate_box_model",
|
|
28
|
+
"SimpleInterpolation",
|
|
29
|
+
"Timer",
|
|
30
|
+
"lerp",
|
|
31
|
+
"smoothstep",
|
|
32
|
+
"ease_in_out",
|
|
33
|
+
"ease_out",
|
|
34
|
+
"ease_in",
|
|
35
|
+
"load_font",
|
|
36
|
+
"outline",
|
|
37
|
+
"shadow",
|
|
38
|
+
"wrap",
|
|
39
|
+
"render_multiline",
|
|
40
|
+
"fit",
|
|
41
|
+
]
|
pygkit/audio/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict, Literal
|
|
3
|
+
|
|
4
|
+
import pygame
|
|
5
|
+
from pygame import Channel, Sound
|
|
6
|
+
|
|
7
|
+
SFX_CHANNELS = 6
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SoundManager:
|
|
11
|
+
def __init__(self, num_channels: int = 8, main_channels: int = 2) -> None:
|
|
12
|
+
if pygame.mixer.get_init() is None:
|
|
13
|
+
raise RuntimeError("pygame.mixer not initialized")
|
|
14
|
+
|
|
15
|
+
if main_channels + SFX_CHANNELS > num_channels:
|
|
16
|
+
num_channels = main_channels + SFX_CHANNELS
|
|
17
|
+
|
|
18
|
+
self._main: Dict[str, Sound] = {}
|
|
19
|
+
self._sfx: Dict[str, Sound] = {}
|
|
20
|
+
|
|
21
|
+
self._num_channels = num_channels
|
|
22
|
+
self._main_count = main_channels
|
|
23
|
+
self._channels = tuple(pygame.Channel(i) for i in range(num_channels))
|
|
24
|
+
self._next_sfx = main_channels
|
|
25
|
+
|
|
26
|
+
def add_sound(
|
|
27
|
+
self, path: Path, key: str, kind: Literal["main", "sfx"] = "sfx"
|
|
28
|
+
) -> bool:
|
|
29
|
+
scope = self._main if kind == "main" else self._sfx
|
|
30
|
+
try:
|
|
31
|
+
scope[key] = pygame.Sound(str(path))
|
|
32
|
+
return True
|
|
33
|
+
except (FileNotFoundError, TypeError, pygame.error) as e:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
def play(
|
|
37
|
+
self,
|
|
38
|
+
key: str,
|
|
39
|
+
kind: Literal["main", "sfx"] = "sfx",
|
|
40
|
+
channel_id: int | None = None,
|
|
41
|
+
loops: int | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
scope = self._main if kind == "main" else self._sfx
|
|
44
|
+
sound = scope.get(key)
|
|
45
|
+
if sound is None:
|
|
46
|
+
raise KeyError(f"Unknown sound '{key}'")
|
|
47
|
+
|
|
48
|
+
if loops is None:
|
|
49
|
+
loops = -1 if kind == "main" else 0
|
|
50
|
+
|
|
51
|
+
if channel_id is not None:
|
|
52
|
+
self._channels[channel_id].stop()
|
|
53
|
+
self._channels[channel_id].play(sound, loops)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
channel = self._assign(kind)
|
|
57
|
+
channel.play(sound, loops)
|
|
58
|
+
|
|
59
|
+
def stop_all(self) -> None:
|
|
60
|
+
for ch in self._channels:
|
|
61
|
+
ch.stop()
|
|
62
|
+
|
|
63
|
+
def stop_channel(self, channel_id: int) -> None:
|
|
64
|
+
self._channels[channel_id].stop()
|
|
65
|
+
|
|
66
|
+
def get_channel(self, channel_id: int) -> Channel:
|
|
67
|
+
return self._channels[channel_id]
|
|
68
|
+
|
|
69
|
+
def _assign(self, kind: Literal["main", "sfx"]) -> Channel:
|
|
70
|
+
if kind == "main":
|
|
71
|
+
for idx in range(self._main_count):
|
|
72
|
+
ch = self._channels[idx]
|
|
73
|
+
if not ch.get_busy():
|
|
74
|
+
return ch
|
|
75
|
+
ch = self._channels[0]
|
|
76
|
+
ch.stop()
|
|
77
|
+
return ch
|
|
78
|
+
|
|
79
|
+
for idx in range(self._main_count, self._num_channels):
|
|
80
|
+
ch = self._channels[idx]
|
|
81
|
+
if not ch.get_busy():
|
|
82
|
+
return ch
|
|
83
|
+
|
|
84
|
+
ch = self._channels[self._next_sfx]
|
|
85
|
+
ch.stop()
|
|
86
|
+
self._next_sfx += 1
|
|
87
|
+
if self._next_sfx >= self._num_channels:
|
|
88
|
+
self._next_sfx = self._main_count
|
|
89
|
+
return ch
|
pygkit/protocols.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from pygame import Surface
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Renderable(Protocol):
|
|
7
|
+
def render(self, screen: Surface, pos_offset: tuple[int, int] = (0, 0)): ...
|
|
8
|
+
|
|
9
|
+
def update(self): ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UIElement(Protocol):
|
|
13
|
+
def render(self, screen: Surface): ...
|
|
14
|
+
|
|
15
|
+
def update(self): ...
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def size(self) -> tuple[int, int]: ...
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def pos(self) -> tuple[int, int]: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Drawable(Protocol):
|
|
25
|
+
def draw(self, surface: Surface): ...
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Updateable(Protocol):
|
|
29
|
+
def update(self): ...
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .base import TransitionRunner, TransitionState
|
|
2
|
+
from .effects import PixelDissolve, Shake
|
|
3
|
+
from .fades import Crossfade, FadeFromBlack, FadeToBlack, Flash
|
|
4
|
+
from .wipes import IrisIn, IrisOut, Slide
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"TransitionRunner",
|
|
8
|
+
"TransitionState",
|
|
9
|
+
"FadeToBlack",
|
|
10
|
+
"FadeFromBlack",
|
|
11
|
+
"Crossfade",
|
|
12
|
+
"Flash",
|
|
13
|
+
"IrisIn",
|
|
14
|
+
"IrisOut",
|
|
15
|
+
"Slide",
|
|
16
|
+
"PixelDissolve",
|
|
17
|
+
"Shake",
|
|
18
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from enum import Enum, auto
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
from pygame import Surface
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Transition(Protocol):
|
|
8
|
+
def start(self, duration: float) -> None: ...
|
|
9
|
+
|
|
10
|
+
def update(self, dt: float) -> None: ...
|
|
11
|
+
|
|
12
|
+
def render(
|
|
13
|
+
self,
|
|
14
|
+
screen: Surface,
|
|
15
|
+
source: Surface | None = None,
|
|
16
|
+
target: Surface | None = None,
|
|
17
|
+
) -> None: ...
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def done(self) -> bool: ...
|
|
21
|
+
|
|
22
|
+
def reset(self) -> None: ...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TransitionState(Enum):
|
|
26
|
+
IDLE = auto()
|
|
27
|
+
OUT = auto()
|
|
28
|
+
IN = auto()
|
|
29
|
+
DONE = auto()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TransitionRunner:
|
|
33
|
+
__slots__ = ("fade_out", "fade_in", "state")
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self, fade_out: Transition | None = None, fade_in: Transition | None = None
|
|
37
|
+
) -> None:
|
|
38
|
+
self.fade_out = fade_out
|
|
39
|
+
self.fade_in = fade_in
|
|
40
|
+
self.state = TransitionState.IDLE
|
|
41
|
+
|
|
42
|
+
def start_out(self, duration: float = 0.5) -> None:
|
|
43
|
+
if self.fade_out is not None:
|
|
44
|
+
self.fade_out.start(duration)
|
|
45
|
+
self.state = TransitionState.OUT
|
|
46
|
+
|
|
47
|
+
def start_in(self, duration: float = 0.5) -> None:
|
|
48
|
+
if self.fade_in is not None:
|
|
49
|
+
self.fade_in.start(duration)
|
|
50
|
+
self.state = TransitionState.IN
|
|
51
|
+
|
|
52
|
+
def update(self, dt: float) -> None:
|
|
53
|
+
if self.state is TransitionState.OUT:
|
|
54
|
+
if self.fade_out is not None:
|
|
55
|
+
self.fade_out.update(dt)
|
|
56
|
+
if self.fade_out.done:
|
|
57
|
+
self.state = TransitionState.IDLE
|
|
58
|
+
else:
|
|
59
|
+
self.state = TransitionState.IDLE
|
|
60
|
+
elif self.state is TransitionState.IN:
|
|
61
|
+
if self.fade_in is not None:
|
|
62
|
+
self.fade_in.update(dt)
|
|
63
|
+
if self.fade_in.done:
|
|
64
|
+
self.state = TransitionState.DONE
|
|
65
|
+
else:
|
|
66
|
+
self.state = TransitionState.DONE
|
|
67
|
+
|
|
68
|
+
def render(
|
|
69
|
+
self,
|
|
70
|
+
screen: Surface,
|
|
71
|
+
source: Surface | None = None,
|
|
72
|
+
target: Surface | None = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
if self.state is TransitionState.OUT and self.fade_out is not None:
|
|
75
|
+
self.fade_out.render(screen, source=source, target=None)
|
|
76
|
+
elif self.state is TransitionState.IN and self.fade_in is not None:
|
|
77
|
+
self.fade_in.render(screen, source=None, target=target)
|
|
78
|
+
elif self.state is TransitionState.DONE and target is not None:
|
|
79
|
+
screen.blit(target, (0, 0))
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def done_out(self) -> bool:
|
|
83
|
+
return (
|
|
84
|
+
self.state is TransitionState.IDLE
|
|
85
|
+
and (self.fade_out is None or self.fade_out.done)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def done(self) -> bool:
|
|
90
|
+
return self.state is TransitionState.DONE
|
|
91
|
+
|
|
92
|
+
def reset(self) -> None:
|
|
93
|
+
if self.fade_out is not None:
|
|
94
|
+
self.fade_out.reset()
|
|
95
|
+
if self.fade_in is not None:
|
|
96
|
+
self.fade_in.reset()
|
|
97
|
+
self.state = TransitionState.IDLE
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
from pygame import Surface
|
|
5
|
+
|
|
6
|
+
from ..utils.interpolation import ease_out
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PixelDissolve:
|
|
10
|
+
"""
|
|
11
|
+
Transitions from source to target by revealing randomly-ordered tiles.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__slots__ = (
|
|
15
|
+
"_tile_size",
|
|
16
|
+
"_easing",
|
|
17
|
+
"_elapsed",
|
|
18
|
+
"_duration",
|
|
19
|
+
"_done",
|
|
20
|
+
"_order",
|
|
21
|
+
"_cols",
|
|
22
|
+
"_rows",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def __init__(self, tile_size: int = 8) -> None:
|
|
26
|
+
self._tile_size = tile_size
|
|
27
|
+
self._easing = ease_out
|
|
28
|
+
self._elapsed = 0.0
|
|
29
|
+
self._duration = 0.0
|
|
30
|
+
self._done = False
|
|
31
|
+
self._order: list[int] = []
|
|
32
|
+
self._cols = 0
|
|
33
|
+
self._rows = 0
|
|
34
|
+
|
|
35
|
+
def start(self, duration: float) -> None:
|
|
36
|
+
self._elapsed = 0.0
|
|
37
|
+
self._duration = duration
|
|
38
|
+
self._done = False
|
|
39
|
+
self._order = []
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def done(self) -> bool:
|
|
43
|
+
return self._done
|
|
44
|
+
|
|
45
|
+
def reset(self) -> None:
|
|
46
|
+
self._elapsed = 0.0
|
|
47
|
+
self._duration = 0.0
|
|
48
|
+
self._done = False
|
|
49
|
+
self._order.clear()
|
|
50
|
+
self._cols = 0
|
|
51
|
+
self._rows = 0
|
|
52
|
+
|
|
53
|
+
def update(self, dt: float) -> None:
|
|
54
|
+
if self._done:
|
|
55
|
+
return
|
|
56
|
+
self._elapsed += dt
|
|
57
|
+
if self._elapsed >= self._duration:
|
|
58
|
+
self._elapsed = self._duration
|
|
59
|
+
self._done = True
|
|
60
|
+
|
|
61
|
+
def render(
|
|
62
|
+
self,
|
|
63
|
+
screen: Surface,
|
|
64
|
+
source: Surface | None = None,
|
|
65
|
+
target: Surface | None = None,
|
|
66
|
+
) -> None:
|
|
67
|
+
w, h = screen.get_size()
|
|
68
|
+
ts = self._tile_size
|
|
69
|
+
cols = max(1, int(math.ceil(w / ts)))
|
|
70
|
+
rows = max(1, int(math.ceil(h / ts)))
|
|
71
|
+
|
|
72
|
+
if not self._order:
|
|
73
|
+
self._cols = cols
|
|
74
|
+
self._rows = rows
|
|
75
|
+
self._order = list(range(cols * rows))
|
|
76
|
+
random.shuffle(self._order)
|
|
77
|
+
|
|
78
|
+
t = self._elapsed / self._duration if self._duration > 0 else 1.0
|
|
79
|
+
reveal_count = int(t * len(self._order))
|
|
80
|
+
|
|
81
|
+
revealed = set(self._order[:reveal_count])
|
|
82
|
+
|
|
83
|
+
for idx in range(cols * rows):
|
|
84
|
+
gx = idx % cols
|
|
85
|
+
gy = idx // cols
|
|
86
|
+
rx = gx * ts
|
|
87
|
+
ry = gy * ts
|
|
88
|
+
|
|
89
|
+
if idx in revealed and target is not None:
|
|
90
|
+
screen.blit(target, (rx, ry), (rx, ry, ts, ts))
|
|
91
|
+
elif source is not None:
|
|
92
|
+
screen.blit(source, (rx, ry), (rx, ry, ts, ts))
|
|
93
|
+
|
|
94
|
+
if self._done and target is not None:
|
|
95
|
+
screen.blit(target, (0, 0))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Shake:
|
|
99
|
+
"""
|
|
100
|
+
Camera shake effect.
|
|
101
|
+
Offsets the screen blit by a decaying random offset.
|
|
102
|
+
Can be used standalone or alongside another transition.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
__slots__ = ("_intensity", "_elapsed", "_duration", "_done", "_offset")
|
|
106
|
+
|
|
107
|
+
def __init__(self, intensity: float = 8.0) -> None:
|
|
108
|
+
self._intensity = intensity
|
|
109
|
+
self._elapsed = 0.0
|
|
110
|
+
self._duration = 0.0
|
|
111
|
+
self._done = False
|
|
112
|
+
self._offset = (0, 0)
|
|
113
|
+
|
|
114
|
+
def start(self, duration: float = 0.3) -> None:
|
|
115
|
+
self._elapsed = 0.0
|
|
116
|
+
self._duration = duration
|
|
117
|
+
self._done = False
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def done(self) -> bool:
|
|
121
|
+
return self._done
|
|
122
|
+
|
|
123
|
+
def reset(self) -> None:
|
|
124
|
+
self._elapsed = 0.0
|
|
125
|
+
self._duration = 0.0
|
|
126
|
+
self._done = False
|
|
127
|
+
self._offset = (0, 0)
|
|
128
|
+
|
|
129
|
+
def update(self, dt: float) -> None:
|
|
130
|
+
if self._done:
|
|
131
|
+
return
|
|
132
|
+
self._elapsed += dt
|
|
133
|
+
if self._elapsed >= self._duration:
|
|
134
|
+
self._elapsed = self._duration
|
|
135
|
+
self._done = True
|
|
136
|
+
self._offset = (0, 0)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
t = self._elapsed / self._duration
|
|
140
|
+
decay = 1.0 - t
|
|
141
|
+
current = self._intensity * decay
|
|
142
|
+
|
|
143
|
+
ox = random.uniform(-current, current)
|
|
144
|
+
oy = random.uniform(-current, current)
|
|
145
|
+
self._offset = (int(ox), int(oy))
|
|
146
|
+
|
|
147
|
+
def render(
|
|
148
|
+
self,
|
|
149
|
+
screen: Surface,
|
|
150
|
+
source: Surface | None = None,
|
|
151
|
+
target: Surface | None = None,
|
|
152
|
+
) -> None:
|
|
153
|
+
surf = target if target is not None else source
|
|
154
|
+
if surf is not None:
|
|
155
|
+
ox, oy = self._offset
|
|
156
|
+
screen.blit(surf, (ox, oy))
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from pygame import SRCALPHA, Surface
|
|
4
|
+
|
|
5
|
+
from ..utils.interpolation import ease_in_out
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _FadeBase:
|
|
9
|
+
__slots__ = ("_color", "_easing", "_overlay", "_elapsed", "_duration", "_done")
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
color: tuple[int, int, int] = (0, 0, 0),
|
|
14
|
+
easing: Callable[[float], float] = ease_in_out,
|
|
15
|
+
) -> None:
|
|
16
|
+
self._color = color
|
|
17
|
+
self._easing = easing
|
|
18
|
+
self._overlay: Surface | None = None
|
|
19
|
+
self._elapsed = 0.0
|
|
20
|
+
self._duration = 0.0
|
|
21
|
+
self._done = True
|
|
22
|
+
|
|
23
|
+
def start(self, duration: float) -> None:
|
|
24
|
+
self._elapsed = 0.0
|
|
25
|
+
self._duration = duration
|
|
26
|
+
self._done = False
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def done(self) -> bool:
|
|
30
|
+
return self._done
|
|
31
|
+
|
|
32
|
+
def reset(self) -> None:
|
|
33
|
+
self._elapsed = 0.0
|
|
34
|
+
self._duration = 0.0
|
|
35
|
+
self._done = False
|
|
36
|
+
self._overlay = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FadeToBlack(_FadeBase):
|
|
40
|
+
"""
|
|
41
|
+
Fades the source surface to black.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def update(self, dt: float) -> None:
|
|
45
|
+
if self._done:
|
|
46
|
+
return
|
|
47
|
+
self._elapsed += dt
|
|
48
|
+
if self._elapsed >= self._duration:
|
|
49
|
+
self._elapsed = self._duration
|
|
50
|
+
self._done = True
|
|
51
|
+
|
|
52
|
+
def render(
|
|
53
|
+
self,
|
|
54
|
+
screen: Surface,
|
|
55
|
+
source: Surface | None = None,
|
|
56
|
+
target: Surface | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
if source is not None:
|
|
59
|
+
screen.blit(source, (0, 0))
|
|
60
|
+
|
|
61
|
+
t = self._easing(self._elapsed / self._duration) if self._duration > 0 else 1.0
|
|
62
|
+
alpha = int(t * 255)
|
|
63
|
+
|
|
64
|
+
if alpha > 0:
|
|
65
|
+
size = screen.get_size()
|
|
66
|
+
if (
|
|
67
|
+
self._overlay is None
|
|
68
|
+
or self._overlay.get_size() != size
|
|
69
|
+
):
|
|
70
|
+
self._overlay = Surface(size, SRCALPHA)
|
|
71
|
+
self._overlay.fill((*self._color, alpha))
|
|
72
|
+
screen.blit(self._overlay, (0, 0))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class FadeFromBlack(_FadeBase):
|
|
76
|
+
"""
|
|
77
|
+
Fades from black to reveal the target surface.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def update(self, dt: float) -> None:
|
|
81
|
+
if self._done:
|
|
82
|
+
return
|
|
83
|
+
self._elapsed += dt
|
|
84
|
+
if self._elapsed >= self._duration:
|
|
85
|
+
self._elapsed = self._duration
|
|
86
|
+
self._done = True
|
|
87
|
+
|
|
88
|
+
def render(
|
|
89
|
+
self,
|
|
90
|
+
screen: Surface,
|
|
91
|
+
source: Surface | None = None,
|
|
92
|
+
target: Surface | None = None,
|
|
93
|
+
) -> None:
|
|
94
|
+
if target is not None:
|
|
95
|
+
screen.blit(target, (0, 0))
|
|
96
|
+
|
|
97
|
+
t = self._easing(self._elapsed / self._duration) if self._duration > 0 else 1.0
|
|
98
|
+
alpha = int((1.0 - t) * 255)
|
|
99
|
+
|
|
100
|
+
if alpha > 0:
|
|
101
|
+
size = screen.get_size()
|
|
102
|
+
if (
|
|
103
|
+
self._overlay is None
|
|
104
|
+
or self._overlay.get_size() != size
|
|
105
|
+
):
|
|
106
|
+
self._overlay = Surface(size, SRCALPHA)
|
|
107
|
+
self._overlay.fill((*self._color, alpha))
|
|
108
|
+
screen.blit(self._overlay, (0, 0))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Crossfade(_FadeBase):
|
|
112
|
+
"""
|
|
113
|
+
Blends from source to target.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, easing: Callable[[float], float] = ease_in_out) -> None:
|
|
117
|
+
super().__init__(easing=easing)
|
|
118
|
+
|
|
119
|
+
def update(self, dt: float) -> None:
|
|
120
|
+
if self._done:
|
|
121
|
+
return
|
|
122
|
+
self._elapsed += dt
|
|
123
|
+
if self._elapsed >= self._duration:
|
|
124
|
+
self._elapsed = self._duration
|
|
125
|
+
self._done = True
|
|
126
|
+
|
|
127
|
+
def render(
|
|
128
|
+
self,
|
|
129
|
+
screen: Surface,
|
|
130
|
+
source: Surface | None = None,
|
|
131
|
+
target: Surface | None = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
t = self._easing(self._elapsed / self._duration) if self._duration > 0 else 1.0
|
|
134
|
+
|
|
135
|
+
if target is not None:
|
|
136
|
+
screen.blit(target, (0, 0))
|
|
137
|
+
|
|
138
|
+
if source is not None and t < 1.0:
|
|
139
|
+
alpha = int((1.0 - t) * 255)
|
|
140
|
+
size = screen.get_size()
|
|
141
|
+
if (
|
|
142
|
+
self._overlay is None
|
|
143
|
+
or self._overlay.get_size() != size
|
|
144
|
+
):
|
|
145
|
+
self._overlay = Surface(size, SRCALPHA)
|
|
146
|
+
self._overlay.fill((0, 0, 0, 0))
|
|
147
|
+
self._overlay.blit(source, (0, 0))
|
|
148
|
+
self._overlay.set_alpha(alpha)
|
|
149
|
+
screen.blit(self._overlay, (0, 0))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class Flash(_FadeBase):
|
|
153
|
+
"""
|
|
154
|
+
Brief white (or colored) flash that fades quickly.
|
|
155
|
+
Default duration 0.15s.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(
|
|
159
|
+
self,
|
|
160
|
+
color: tuple[int, int, int] = (255, 255, 255),
|
|
161
|
+
easing: Callable[[float], float] = ease_in_out,
|
|
162
|
+
) -> None:
|
|
163
|
+
super().__init__(color=color, easing=easing)
|
|
164
|
+
|
|
165
|
+
def start(self, duration: float | None = None) -> None:
|
|
166
|
+
super().start(0.15 if duration is None else duration)
|
|
167
|
+
|
|
168
|
+
def update(self, dt: float) -> None:
|
|
169
|
+
if self._done:
|
|
170
|
+
return
|
|
171
|
+
self._elapsed += dt
|
|
172
|
+
if self._elapsed >= self._duration:
|
|
173
|
+
self._elapsed = self._duration
|
|
174
|
+
self._done = True
|
|
175
|
+
|
|
176
|
+
def render(
|
|
177
|
+
self,
|
|
178
|
+
screen: Surface,
|
|
179
|
+
source: Surface | None = None,
|
|
180
|
+
target: Surface | None = None,
|
|
181
|
+
) -> None:
|
|
182
|
+
if source is not None:
|
|
183
|
+
screen.blit(source, (0, 0))
|
|
184
|
+
|
|
185
|
+
t = self._easing(self._elapsed / self._duration) if self._duration > 0 else 1.0
|
|
186
|
+
alpha = int((1.0 - t) * 255)
|
|
187
|
+
|
|
188
|
+
if alpha > 0:
|
|
189
|
+
size = screen.get_size()
|
|
190
|
+
if (
|
|
191
|
+
self._overlay is None
|
|
192
|
+
or self._overlay.get_size() != size
|
|
193
|
+
):
|
|
194
|
+
self._overlay = Surface(size, SRCALPHA)
|
|
195
|
+
self._overlay.fill((*self._color, alpha))
|
|
196
|
+
screen.blit(self._overlay, (0, 0))
|