pygkit 1.0.8__tar.gz → 2.1.0__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.
Files changed (76) hide show
  1. {pygkit-1.0.8 → pygkit-2.1.0}/PKG-INFO +21 -4
  2. {pygkit-1.0.8 → pygkit-2.1.0}/README.md +11 -3
  3. {pygkit-1.0.8 → pygkit-2.1.0}/pyproject.toml +17 -1
  4. pygkit-2.1.0/src/pygkit/__init__.py +231 -0
  5. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/animation/player.py +29 -24
  6. pygkit-2.1.0/src/pygkit/audio/__init__.py +7 -0
  7. pygkit-2.1.0/src/pygkit/audio/soundmanager.py +262 -0
  8. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/inventory/__init__.py +1 -16
  9. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/inventory/assets.py +1 -4
  10. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/inventory/core.py +17 -8
  11. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/inventory/data.py +1 -2
  12. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/inventory/ui.py +27 -14
  13. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/lighting/__init__.py +4 -4
  14. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/lighting/lightmap.py +9 -3
  15. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/lighting/lights.py +269 -56
  16. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/lighting/sprites.py +11 -1
  17. pygkit-2.1.0/src/pygkit/parallax/__init__.py +8 -0
  18. pygkit-2.1.0/src/pygkit/parallax/background.py +139 -0
  19. pygkit-2.1.0/src/pygkit/parallax/camera.py +182 -0
  20. pygkit-2.1.0/src/pygkit/protocols.py +49 -0
  21. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/signals/core.py +34 -8
  22. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/transitions/base.py +38 -2
  23. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/transitions/effects.py +59 -25
  24. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/transitions/fades.py +44 -28
  25. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/transitions/wipes.py +46 -25
  26. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/ui/__init__.py +12 -0
  27. pygkit-2.1.0/src/pygkit/ui/anchors.py +80 -0
  28. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/ui/base.py +5 -5
  29. pygkit-2.1.0/src/pygkit/ui/button.py +140 -0
  30. pygkit-2.1.0/src/pygkit/ui/container.py +54 -0
  31. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/ui/cooldown.py +11 -6
  32. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/ui/dialog.py +47 -71
  33. pygkit-2.1.0/src/pygkit/ui/menubar.py +118 -0
  34. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/ui/progressbar.py +3 -3
  35. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/utils/interpolation.py +3 -3
  36. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/utils/text.py +8 -11
  37. pygkit-2.1.0/src/pygkit/utils/timer.py +70 -0
  38. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit.egg-info/PKG-INFO +21 -4
  39. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit.egg-info/SOURCES.txt +14 -0
  40. pygkit-2.1.0/src/pygkit.egg-info/requires.txt +18 -0
  41. pygkit-2.1.0/tests/test_anchors.py +39 -0
  42. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_animation.py +25 -16
  43. pygkit-2.1.0/tests/test_audio.py +200 -0
  44. pygkit-2.1.0/tests/test_button.py +104 -0
  45. pygkit-2.1.0/tests/test_container.py +69 -0
  46. pygkit-2.1.0/tests/test_cooldown.py +42 -0
  47. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_dialog.py +24 -1
  48. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_interpolation.py +17 -8
  49. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_inventory.py +176 -0
  50. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_lighting.py +66 -0
  51. pygkit-2.1.0/tests/test_menubar.py +132 -0
  52. pygkit-2.1.0/tests/test_packaging.py +106 -0
  53. pygkit-2.1.0/tests/test_parallax.py +280 -0
  54. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_signals.py +21 -5
  55. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_text.py +1 -1
  56. pygkit-2.1.0/tests/test_timer.py +112 -0
  57. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_transitions.py +37 -1
  58. pygkit-1.0.8/src/pygkit/__init__.py +0 -95
  59. pygkit-1.0.8/src/pygkit/audio/__init__.py +0 -6
  60. pygkit-1.0.8/src/pygkit/audio/soundmanager.py +0 -89
  61. pygkit-1.0.8/src/pygkit/protocols.py +0 -29
  62. pygkit-1.0.8/src/pygkit/ui/container.py +0 -21
  63. pygkit-1.0.8/src/pygkit/utils/timer.py +0 -35
  64. pygkit-1.0.8/src/pygkit.egg-info/requires.txt +0 -1
  65. pygkit-1.0.8/tests/test_timer.py +0 -42
  66. {pygkit-1.0.8 → pygkit-2.1.0}/LICENSE +0 -0
  67. {pygkit-1.0.8 → pygkit-2.1.0}/setup.cfg +0 -0
  68. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/animation/__init__.py +0 -0
  69. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/animation/sheet.py +0 -0
  70. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/lighting/blend.py +0 -0
  71. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/signals/__init__.py +0 -0
  72. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/transitions/__init__.py +0 -0
  73. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit/utils/__init__.py +0 -0
  74. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit.egg-info/dependency_links.txt +0 -0
  75. {pygkit-1.0.8 → pygkit-2.1.0}/src/pygkit.egg-info/top_level.txt +0 -0
  76. {pygkit-1.0.8 → pygkit-2.1.0}/tests/test_box_model.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygkit
3
- Version: 1.0.8
3
+ Version: 2.1.0
4
4
  Summary: General-purpose Pygame CE runtime kit: UI widgets, audio, timers, interpolation, and more.
5
5
  Author: pygkit contributors
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -694,6 +694,15 @@ Requires-Python: >=3.10
694
694
  Description-Content-Type: text/markdown
695
695
  License-File: LICENSE
696
696
  Requires-Dist: pygame-ce>=2.5
697
+ Provides-Extra: ui
698
+ Provides-Extra: audio
699
+ Provides-Extra: animation
700
+ Provides-Extra: lighting
701
+ Provides-Extra: transitions
702
+ Provides-Extra: inventory
703
+ Provides-Extra: parallax
704
+ Provides-Extra: all
705
+ Requires-Dist: pygkit[animation,audio,inventory,lighting,parallax,transitions,ui]; extra == "all"
697
706
  Dynamic: license-file
698
707
 
699
708
  # pygkit
@@ -708,11 +717,11 @@ import pygkit
708
717
 
709
718
  ## Features
710
719
 
711
- - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system
720
+ - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system, buttons, menubar container, anchor/row/grid placement helpers
712
721
  - **Animation**: Multi-state `AnimationPlayer` where every state owns its own spritesheet — mixed image sizes and grids (e.g. `idle_spritesheet.png` vs `run_spritesheet.png`) in one player; pure image API, no JSON
713
722
  - **Dialog**: Typewriter effect with customizable speeds, speaker names, and progress indicators
714
723
  - **Inventory**: Grid-based item management with stacking, weight tracking, events, and serialization
715
- - **Audio**: SoundManager with channel pools and explicit targeting
724
+ - **Audio**: SoundManager with channel pools, kind/key addressing, volume buses, fades
716
725
  - **Lighting**: low-resolution light map pipeline (`LightMap`), point lights
717
726
  with flicker, wind-reactive `DynamicLight` flames, and cone `Spotlight` —
718
727
  full RGB/RGBA blend modes, allocation-free per frame
@@ -722,5 +731,13 @@ import pygkit
722
731
  ## Installation
723
732
 
724
733
  ```bash
725
- pip install pygkit
734
+ pip install pygkit # core: signals, utils, protocols
735
+ pip install "pygkit[ui]" # widgets, dialog, cooldowns
736
+ pip install "pygkit[all]" # everything
726
737
  ```
738
+
739
+ Available extras: `ui`, `audio`, `animation`, `lighting`,
740
+ `transitions`, `inventory`, `parallax`, `all`. `import pygkit` alone loads
741
+ core only; subsystems load lazily on first use.
742
+
743
+ > Extra: lightning idea was extended version of [https://www.youtube.com/watch?v=LN7u20H7zK4](https://www.youtube.com/watch?v=LN7u20H7zK4) which was my first attempt of making lightning after learning blend modes.
@@ -10,11 +10,11 @@ import pygkit
10
10
 
11
11
  ## Features
12
12
 
13
- - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system
13
+ - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system, buttons, menubar container, anchor/row/grid placement helpers
14
14
  - **Animation**: Multi-state `AnimationPlayer` where every state owns its own spritesheet — mixed image sizes and grids (e.g. `idle_spritesheet.png` vs `run_spritesheet.png`) in one player; pure image API, no JSON
15
15
  - **Dialog**: Typewriter effect with customizable speeds, speaker names, and progress indicators
16
16
  - **Inventory**: Grid-based item management with stacking, weight tracking, events, and serialization
17
- - **Audio**: SoundManager with channel pools and explicit targeting
17
+ - **Audio**: SoundManager with channel pools, kind/key addressing, volume buses, fades
18
18
  - **Lighting**: low-resolution light map pipeline (`LightMap`), point lights
19
19
  with flicker, wind-reactive `DynamicLight` flames, and cone `Spotlight` —
20
20
  full RGB/RGBA blend modes, allocation-free per frame
@@ -24,5 +24,13 @@ import pygkit
24
24
  ## Installation
25
25
 
26
26
  ```bash
27
- pip install pygkit
27
+ pip install pygkit # core: signals, utils, protocols
28
+ pip install "pygkit[ui]" # widgets, dialog, cooldowns
29
+ pip install "pygkit[all]" # everything
28
30
  ```
31
+
32
+ Available extras: `ui`, `audio`, `animation`, `lighting`,
33
+ `transitions`, `inventory`, `parallax`, `all`. `import pygkit` alone loads
34
+ core only; subsystems load lazily on first use.
35
+
36
+ > Extra: lightning idea was extended version of [https://www.youtube.com/watch?v=LN7u20H7zK4](https://www.youtube.com/watch?v=LN7u20H7zK4) which was my first attempt of making lightning after learning blend modes.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pygkit"
7
- version = "1.0.8"
7
+ version = "2.1.0"
8
8
  description = "General-purpose Pygame CE runtime kit: UI widgets, audio, timers, interpolation, and more."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -26,6 +26,22 @@ classifiers = [
26
26
  "Topic :: Software Development :: Libraries",
27
27
  ]
28
28
 
29
+ [project.optional-dependencies]
30
+ # One extra per subsystem. Empty for now: every subsystem is pure
31
+ # pygame-ce + stdlib. Slots are reserved for future speedups
32
+ # (lighting color math -> numpy, sprite pack -> pillow) without
33
+ # taxing core installs.
34
+ ui = []
35
+ audio = []
36
+ animation = []
37
+ lighting = []
38
+ transitions = []
39
+ inventory = []
40
+ parallax = []
41
+ all = [
42
+ "pygkit[ui, audio, animation, lighting, transitions, inventory, parallax]",
43
+ ]
44
+
29
45
  [project.urls]
30
46
  Homepage = "https://github.com/FluffyBrudy/pygkit"
31
47
  Repository = "https://github.com/FluffyBrudy/pygkit"
@@ -0,0 +1,231 @@
1
+ """pygkit - Pygame CE runtime kit.
2
+
3
+ Core (signals, utils, protocols) imports eagerly. Subsystems load
4
+ lazily on first attribute use, so ``import pygkit`` stays cheap::
5
+
6
+ pip install pygkit # core only
7
+ pip install "pygkit[ui]" # + widgets, dialog, cooldowns
8
+ pip install "pygkit[all]" # everything
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib
14
+ from typing import Any
15
+
16
+ from .protocols import Clickable, Drawable, Renderable, UIElement, Updateable, Widget
17
+ from .signals import Signal, SignalBus, signal
18
+ from .utils import (
19
+ SimpleInterpolation,
20
+ Timer,
21
+ ease_in,
22
+ ease_in_out,
23
+ ease_out,
24
+ fit,
25
+ lerp,
26
+ load_font,
27
+ outline,
28
+ render_multiline,
29
+ shadow,
30
+ smoothstep,
31
+ wrap,
32
+ )
33
+
34
+ _LAZY_ATTRS: dict[str, str] = {
35
+ "SoundManager": "audio",
36
+ "SFX_CHANNELS": "audio",
37
+ "DEFAULT_SFX_CHANNELS": "audio",
38
+ "ParallaxBackground": "parallax",
39
+ "ParallaxLayer": "parallax",
40
+ "Camera2D": "parallax",
41
+ "AnimationPlayer": "animation",
42
+ "AnimationSheet": "animation",
43
+ "PackOptions": "animation",
44
+ "pack_spritesheet": "animation",
45
+ "LightMap": "lighting",
46
+ "PointLight": "lighting",
47
+ "Spotlight": "lighting",
48
+ "DynamicLight": "lighting",
49
+ "glow_sprite": "lighting",
50
+ "spotlight_sprite": "lighting",
51
+ "dynamic_glow_sprite": "lighting",
52
+ "clear_sprite_cache": "lighting",
53
+ "RGB_ADD": "lighting",
54
+ "RGB_SUB": "lighting",
55
+ "RGB_MULT": "lighting",
56
+ "RGB_MIN": "lighting",
57
+ "RGB_MAX": "lighting",
58
+ "RGBA_ADD": "lighting",
59
+ "RGBA_SUB": "lighting",
60
+ "RGBA_MULT": "lighting",
61
+ "RGBA_MIN": "lighting",
62
+ "RGBA_MAX": "lighting",
63
+ "LIGHT_ADD": "lighting",
64
+ "SCENE_MULT": "lighting",
65
+ "TransitionRunner": "transitions",
66
+ "TransitionState": "transitions",
67
+ "FadeToBlack": "transitions",
68
+ "FadeFromBlack": "transitions",
69
+ "Crossfade": "transitions",
70
+ "Flash": "transitions",
71
+ "IrisIn": "transitions",
72
+ "IrisOut": "transitions",
73
+ "Slide": "transitions",
74
+ "PixelDissolve": "transitions",
75
+ "Shake": "transitions",
76
+ "Container": "ui",
77
+ "CooldownOverlay": "ui",
78
+ "ProgressBarUI": "ui",
79
+ "UIBase": "ui",
80
+ "UIOptions": "ui",
81
+ "BoxModel": "ui",
82
+ "BoxModelResult": "ui",
83
+ "generate_box_model": "ui",
84
+ "DialogBox": "ui",
85
+ "DialogConfig": "ui",
86
+ "DialogColors": "ui",
87
+ "DialogLine": "ui",
88
+ "TypewriterSpeed": "ui",
89
+ "Button": "ui",
90
+ "ButtonStyle": "ui",
91
+ "Menubar": "ui",
92
+ "ANCHORS": "ui",
93
+ "anchor_point": "ui",
94
+ "place": "ui",
95
+ "row": "ui",
96
+ "column": "ui",
97
+ "grid": "ui",
98
+ "ItemData": "inventory",
99
+ "InventoryConfig": "inventory",
100
+ "ItemStack": "inventory",
101
+ "InventoryEvent": "inventory",
102
+ "RARITY_COLORS": "inventory",
103
+ "DEFAULT_INVENTORY_CONFIG": "inventory",
104
+ "Inventory": "inventory",
105
+ "AssetCache": "inventory",
106
+ "get_cache": "inventory",
107
+ "generate_procedural_icon": "inventory",
108
+ "render_quantity_text": "inventory",
109
+ "Tooltip": "inventory",
110
+ "DragDropState": "inventory",
111
+ "InventoryWidget": "inventory",
112
+ "InventoryManager": "inventory",
113
+ }
114
+
115
+ __all__ = [
116
+ "SoundManager",
117
+ "SFX_CHANNELS",
118
+ "DEFAULT_SFX_CHANNELS",
119
+ "ParallaxBackground",
120
+ "ParallaxLayer",
121
+ "Camera2D",
122
+ "AnimationPlayer",
123
+ "AnimationSheet",
124
+ "PackOptions",
125
+ "pack_spritesheet",
126
+ "LightMap",
127
+ "PointLight",
128
+ "Spotlight",
129
+ "DynamicLight",
130
+ "glow_sprite",
131
+ "spotlight_sprite",
132
+ "dynamic_glow_sprite",
133
+ "clear_sprite_cache",
134
+ "RGB_ADD",
135
+ "RGB_SUB",
136
+ "RGB_MULT",
137
+ "RGB_MIN",
138
+ "RGB_MAX",
139
+ "RGBA_ADD",
140
+ "RGBA_SUB",
141
+ "RGBA_MULT",
142
+ "RGBA_MIN",
143
+ "RGBA_MAX",
144
+ "LIGHT_ADD",
145
+ "SCENE_MULT",
146
+ "TransitionRunner",
147
+ "TransitionState",
148
+ "FadeToBlack",
149
+ "FadeFromBlack",
150
+ "Crossfade",
151
+ "Flash",
152
+ "IrisIn",
153
+ "IrisOut",
154
+ "Slide",
155
+ "PixelDissolve",
156
+ "Shake",
157
+ "Container",
158
+ "CooldownOverlay",
159
+ "ProgressBarUI",
160
+ "UIBase",
161
+ "UIOptions",
162
+ "BoxModel",
163
+ "BoxModelResult",
164
+ "generate_box_model",
165
+ "DialogBox",
166
+ "DialogConfig",
167
+ "DialogColors",
168
+ "DialogLine",
169
+ "TypewriterSpeed",
170
+ "Button",
171
+ "ButtonStyle",
172
+ "Menubar",
173
+ "ANCHORS",
174
+ "anchor_point",
175
+ "place",
176
+ "row",
177
+ "column",
178
+ "grid",
179
+ "ItemData",
180
+ "InventoryConfig",
181
+ "ItemStack",
182
+ "InventoryEvent",
183
+ "RARITY_COLORS",
184
+ "DEFAULT_INVENTORY_CONFIG",
185
+ "Inventory",
186
+ "AssetCache",
187
+ "get_cache",
188
+ "generate_procedural_icon",
189
+ "render_quantity_text",
190
+ "Tooltip",
191
+ "DragDropState",
192
+ "InventoryWidget",
193
+ "InventoryManager",
194
+ "SimpleInterpolation",
195
+ "Timer",
196
+ "lerp",
197
+ "smoothstep",
198
+ "ease_in_out",
199
+ "ease_out",
200
+ "ease_in",
201
+ "load_font",
202
+ "outline",
203
+ "shadow",
204
+ "wrap",
205
+ "render_multiline",
206
+ "fit",
207
+ "Signal",
208
+ "SignalBus",
209
+ "signal",
210
+ "Renderable",
211
+ "UIElement",
212
+ "Drawable",
213
+ "Updateable",
214
+ "Clickable",
215
+ "Widget",
216
+ ]
217
+
218
+
219
+ def __getattr__(name: str) -> Any:
220
+ try:
221
+ sub = _LAZY_ATTRS[name]
222
+ except KeyError:
223
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
224
+ module = importlib.import_module(f"{__name__}.{sub}")
225
+ value = getattr(module, name)
226
+ globals()[name] = value
227
+ return value
228
+
229
+
230
+ def __dir__() -> list[str]:
231
+ return sorted(__all__)
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import math
3
4
  from dataclasses import dataclass
4
5
  from typing import Callable
5
6
 
6
7
  from .sheet import AnimationSheet
7
8
 
9
+ _UNSET: object = object()
10
+
8
11
 
9
12
  @dataclass
10
13
  class _State:
@@ -19,7 +22,7 @@ class AnimationPlayer:
19
22
 
20
23
  States are registered lazily via :meth:`play`; sheets may differ freely in
21
24
  pixel size and grid layout between states. Playback is time-based: feed
22
- ``update(dt_ms)`` each frame and blit ``image``.
25
+ ``update(dt)`` each frame with delta time in seconds and blit ``image``.
23
26
  """
24
27
 
25
28
  __slots__ = (
@@ -89,13 +92,15 @@ class AnimationPlayer:
89
92
  *,
90
93
  fps: float | None = None,
91
94
  loop: bool | None = None,
92
- on_finish: Callable[[], None] | None = None,
95
+ on_finish: Callable[[], None] | None | object = _UNSET,
96
+ restart: bool = False,
93
97
  ) -> None:
94
98
  """Switch to *name*, registering the state on first use.
95
99
 
96
100
  Registration requires *sheet*; omitted options fall back to the
97
101
  player defaults given at construction. Calling ``play`` for the state
98
- already playing does not restart it — use :meth:`reset`.
102
+ already playing does not restart it unless ``restart`` is true.
103
+ Pass ``on_finish=None`` explicitly to clear a default callback.
99
104
  """
100
105
  if name not in self._states:
101
106
  if sheet is None:
@@ -106,37 +111,37 @@ class AnimationPlayer:
106
111
  sheet=sheet,
107
112
  fps=float(resolved_fps),
108
113
  loop=self._default_loop if loop is None else bool(loop),
109
- on_finish=self._default_on_finish if on_finish is None else on_finish,
114
+ on_finish=self._default_on_finish if on_finish is _UNSET else on_finish,
110
115
  )
111
116
 
112
- if name == self._state_name:
117
+ if name == self._state_name and not restart:
113
118
  return
114
119
  self._state_name = name
115
120
  self.reset()
116
121
 
117
- def update(self, dt_ms: float) -> None:
118
- """Advance playback by *dt_ms* milliseconds of wall time."""
122
+ def update(self, dt: float) -> None:
123
+ """Advance playback by *dt* seconds."""
119
124
  if self._paused or self._state_name is None or self._finished:
120
125
  return
126
+ if not math.isfinite(dt) or dt < 0:
127
+ dt = 0.0
121
128
  state = self._states[self._state_name]
122
129
  frame_count = state.sheet.frame_count
123
- frame_duration = 1000.0 / state.fps
124
- self._elapsed_in_frame += max(0.0, dt_ms)
125
- while self._elapsed_in_frame >= frame_duration:
126
- self._elapsed_in_frame -= frame_duration
127
- next_index = self._frame_index + 1
128
- if next_index < frame_count:
129
- self._frame_index = next_index
130
- continue
131
- if state.loop:
132
- self._frame_index = 0
133
- else:
134
- self._frame_index = frame_count - 1
135
- self._elapsed_in_frame = 0.0
136
- self._finished = True
137
- if state.on_finish is not None:
138
- state.on_finish()
139
- break
130
+ frame_duration = 1.0 / state.fps
131
+ steps, self._elapsed_in_frame = divmod(self._elapsed_in_frame + dt, frame_duration)
132
+ steps = int(steps)
133
+ if steps <= 0:
134
+ return
135
+ if state.loop:
136
+ self._frame_index = (self._frame_index + steps) % frame_count
137
+ elif self._frame_index + steps >= frame_count:
138
+ self._frame_index = frame_count - 1
139
+ self._elapsed_in_frame = 0.0
140
+ self._finished = True
141
+ if state.on_finish is not None:
142
+ state.on_finish()
143
+ else:
144
+ self._frame_index += steps
140
145
 
141
146
  def pause(self) -> None:
142
147
  self._paused = True
@@ -0,0 +1,7 @@
1
+ from .soundmanager import DEFAULT_SFX_CHANNELS, SFX_CHANNELS, SoundManager
2
+
3
+ __all__ = [
4
+ "SoundManager",
5
+ "SFX_CHANNELS",
6
+ "DEFAULT_SFX_CHANNELS",
7
+ ]