pygkit 1.0.7__tar.gz → 1.2.1__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 (64) hide show
  1. {pygkit-1.0.7 → pygkit-1.2.1}/PKG-INFO +17 -3
  2. {pygkit-1.0.7 → pygkit-1.2.1}/README.md +8 -2
  3. {pygkit-1.0.7 → pygkit-1.2.1}/pyproject.toml +16 -1
  4. pygkit-1.2.1/src/pygkit/__init__.py +223 -0
  5. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/animation/sheet.py +72 -15
  6. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/inventory/__init__.py +1 -16
  7. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/inventory/assets.py +1 -4
  8. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/inventory/core.py +1 -2
  9. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/inventory/data.py +1 -2
  10. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/lighting/__init__.py +4 -4
  11. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/lighting/lightmap.py +1 -1
  12. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/lighting/lights.py +3 -3
  13. pygkit-1.2.1/src/pygkit/protocols.py +49 -0
  14. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/ui/__init__.py +12 -0
  15. pygkit-1.2.1/src/pygkit/ui/anchors.py +80 -0
  16. pygkit-1.2.1/src/pygkit/ui/button.py +134 -0
  17. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/ui/dialog.py +1 -26
  18. pygkit-1.2.1/src/pygkit/ui/menubar.py +117 -0
  19. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit.egg-info/PKG-INFO +17 -3
  20. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit.egg-info/SOURCES.txt +7 -0
  21. pygkit-1.2.1/src/pygkit.egg-info/requires.txt +16 -0
  22. pygkit-1.2.1/tests/test_anchors.py +39 -0
  23. pygkit-1.2.1/tests/test_button.py +88 -0
  24. pygkit-1.2.1/tests/test_menubar.py +132 -0
  25. pygkit-1.2.1/tests/test_packaging.py +106 -0
  26. pygkit-1.0.7/src/pygkit/__init__.py +0 -95
  27. pygkit-1.0.7/src/pygkit/protocols.py +0 -29
  28. pygkit-1.0.7/src/pygkit.egg-info/requires.txt +0 -1
  29. {pygkit-1.0.7 → pygkit-1.2.1}/LICENSE +0 -0
  30. {pygkit-1.0.7 → pygkit-1.2.1}/setup.cfg +0 -0
  31. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/animation/__init__.py +0 -0
  32. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/animation/player.py +0 -0
  33. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/audio/__init__.py +0 -0
  34. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/audio/soundmanager.py +0 -0
  35. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/inventory/ui.py +0 -0
  36. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/lighting/blend.py +0 -0
  37. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/lighting/sprites.py +0 -0
  38. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/signals/__init__.py +0 -0
  39. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/signals/core.py +0 -0
  40. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/transitions/__init__.py +0 -0
  41. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/transitions/base.py +0 -0
  42. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/transitions/effects.py +0 -0
  43. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/transitions/fades.py +0 -0
  44. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/transitions/wipes.py +0 -0
  45. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/ui/base.py +0 -0
  46. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/ui/container.py +0 -0
  47. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/ui/cooldown.py +0 -0
  48. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/ui/progressbar.py +0 -0
  49. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/utils/__init__.py +0 -0
  50. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/utils/interpolation.py +0 -0
  51. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/utils/text.py +0 -0
  52. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit/utils/timer.py +0 -0
  53. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit.egg-info/dependency_links.txt +0 -0
  54. {pygkit-1.0.7 → pygkit-1.2.1}/src/pygkit.egg-info/top_level.txt +0 -0
  55. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_animation.py +0 -0
  56. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_box_model.py +0 -0
  57. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_dialog.py +0 -0
  58. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_interpolation.py +0 -0
  59. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_inventory.py +0 -0
  60. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_lighting.py +0 -0
  61. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_signals.py +0 -0
  62. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_text.py +0 -0
  63. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_timer.py +0 -0
  64. {pygkit-1.0.7 → pygkit-1.2.1}/tests/test_transitions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygkit
3
- Version: 1.0.7
3
+ Version: 1.2.1
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,14 @@ 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: all
704
+ Requires-Dist: pygkit[animation,audio,inventory,lighting,transitions,ui]; extra == "all"
697
705
  Dynamic: license-file
698
706
 
699
707
  # pygkit
@@ -708,7 +716,7 @@ import pygkit
708
716
 
709
717
  ## Features
710
718
 
711
- - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system
719
+ - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system, buttons, menubar container, anchor/row/grid placement helpers
712
720
  - **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
721
  - **Dialog**: Typewriter effect with customizable speeds, speaker names, and progress indicators
714
722
  - **Inventory**: Grid-based item management with stacking, weight tracking, events, and serialization
@@ -722,5 +730,11 @@ import pygkit
722
730
  ## Installation
723
731
 
724
732
  ```bash
725
- pip install pygkit
733
+ pip install pygkit # core: signals, utils, protocols
734
+ pip install "pygkit[ui]" # widgets, dialog, cooldowns
735
+ pip install "pygkit[all]" # everything
726
736
  ```
737
+
738
+ Available extras: `ui`, `audio`, `animation`, `lighting`,
739
+ `transitions`, `inventory`, `all`. `import pygkit` alone loads
740
+ core only; subsystems load lazily on first use.
@@ -10,7 +10,7 @@ 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
@@ -24,5 +24,11 @@ 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`, `all`. `import pygkit` alone loads
34
+ core only; subsystems load lazily on first use.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pygkit"
7
- version = "1.0.7"
7
+ version = "1.2.1"
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,21 @@ 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
+ all = [
41
+ "pygkit[ui, audio, animation, lighting, transitions, inventory]",
42
+ ]
43
+
29
44
  [project.urls]
30
45
  Homepage = "https://github.com/FluffyBrudy/pygkit"
31
46
  Repository = "https://github.com/FluffyBrudy/pygkit"
@@ -0,0 +1,223 @@
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
+ "AnimationPlayer": "animation",
38
+ "AnimationSheet": "animation",
39
+ "PackOptions": "animation",
40
+ "pack_spritesheet": "animation",
41
+ "LightMap": "lighting",
42
+ "PointLight": "lighting",
43
+ "Spotlight": "lighting",
44
+ "DynamicLight": "lighting",
45
+ "glow_sprite": "lighting",
46
+ "spotlight_sprite": "lighting",
47
+ "dynamic_glow_sprite": "lighting",
48
+ "clear_sprite_cache": "lighting",
49
+ "RGB_ADD": "lighting",
50
+ "RGB_SUB": "lighting",
51
+ "RGB_MULT": "lighting",
52
+ "RGB_MIN": "lighting",
53
+ "RGB_MAX": "lighting",
54
+ "RGBA_ADD": "lighting",
55
+ "RGBA_SUB": "lighting",
56
+ "RGBA_MULT": "lighting",
57
+ "RGBA_MIN": "lighting",
58
+ "RGBA_MAX": "lighting",
59
+ "LIGHT_ADD": "lighting",
60
+ "SCENE_MULT": "lighting",
61
+ "TransitionRunner": "transitions",
62
+ "TransitionState": "transitions",
63
+ "FadeToBlack": "transitions",
64
+ "FadeFromBlack": "transitions",
65
+ "Crossfade": "transitions",
66
+ "Flash": "transitions",
67
+ "IrisIn": "transitions",
68
+ "IrisOut": "transitions",
69
+ "Slide": "transitions",
70
+ "PixelDissolve": "transitions",
71
+ "Shake": "transitions",
72
+ "Container": "ui",
73
+ "CooldownOverlay": "ui",
74
+ "ProgressBarUI": "ui",
75
+ "UIBase": "ui",
76
+ "UIOptions": "ui",
77
+ "BoxModel": "ui",
78
+ "BoxModelResult": "ui",
79
+ "generate_box_model": "ui",
80
+ "DialogBox": "ui",
81
+ "DialogConfig": "ui",
82
+ "DialogColors": "ui",
83
+ "DialogLine": "ui",
84
+ "TypewriterSpeed": "ui",
85
+ "Button": "ui",
86
+ "ButtonStyle": "ui",
87
+ "Menubar": "ui",
88
+ "ANCHORS": "ui",
89
+ "anchor_point": "ui",
90
+ "place": "ui",
91
+ "row": "ui",
92
+ "column": "ui",
93
+ "grid": "ui",
94
+ "ItemData": "inventory",
95
+ "InventoryConfig": "inventory",
96
+ "ItemStack": "inventory",
97
+ "InventoryEvent": "inventory",
98
+ "RARITY_COLORS": "inventory",
99
+ "DEFAULT_INVENTORY_CONFIG": "inventory",
100
+ "Inventory": "inventory",
101
+ "AssetCache": "inventory",
102
+ "get_cache": "inventory",
103
+ "generate_procedural_icon": "inventory",
104
+ "render_quantity_text": "inventory",
105
+ "Tooltip": "inventory",
106
+ "DragDropState": "inventory",
107
+ "InventoryWidget": "inventory",
108
+ "InventoryManager": "inventory",
109
+ }
110
+
111
+ __all__ = [
112
+ "SoundManager",
113
+ "SFX_CHANNELS",
114
+ "AnimationPlayer",
115
+ "AnimationSheet",
116
+ "PackOptions",
117
+ "pack_spritesheet",
118
+ "LightMap",
119
+ "PointLight",
120
+ "Spotlight",
121
+ "DynamicLight",
122
+ "glow_sprite",
123
+ "spotlight_sprite",
124
+ "dynamic_glow_sprite",
125
+ "clear_sprite_cache",
126
+ "RGB_ADD",
127
+ "RGB_SUB",
128
+ "RGB_MULT",
129
+ "RGB_MIN",
130
+ "RGB_MAX",
131
+ "RGBA_ADD",
132
+ "RGBA_SUB",
133
+ "RGBA_MULT",
134
+ "RGBA_MIN",
135
+ "RGBA_MAX",
136
+ "LIGHT_ADD",
137
+ "SCENE_MULT",
138
+ "TransitionRunner",
139
+ "TransitionState",
140
+ "FadeToBlack",
141
+ "FadeFromBlack",
142
+ "Crossfade",
143
+ "Flash",
144
+ "IrisIn",
145
+ "IrisOut",
146
+ "Slide",
147
+ "PixelDissolve",
148
+ "Shake",
149
+ "Container",
150
+ "CooldownOverlay",
151
+ "ProgressBarUI",
152
+ "UIBase",
153
+ "UIOptions",
154
+ "BoxModel",
155
+ "BoxModelResult",
156
+ "generate_box_model",
157
+ "DialogBox",
158
+ "DialogConfig",
159
+ "DialogColors",
160
+ "DialogLine",
161
+ "TypewriterSpeed",
162
+ "Button",
163
+ "ButtonStyle",
164
+ "Menubar",
165
+ "ANCHORS",
166
+ "anchor_point",
167
+ "place",
168
+ "row",
169
+ "column",
170
+ "grid",
171
+ "ItemData",
172
+ "InventoryConfig",
173
+ "ItemStack",
174
+ "InventoryEvent",
175
+ "RARITY_COLORS",
176
+ "DEFAULT_INVENTORY_CONFIG",
177
+ "Inventory",
178
+ "AssetCache",
179
+ "get_cache",
180
+ "generate_procedural_icon",
181
+ "render_quantity_text",
182
+ "Tooltip",
183
+ "DragDropState",
184
+ "InventoryWidget",
185
+ "InventoryManager",
186
+ "SimpleInterpolation",
187
+ "Timer",
188
+ "lerp",
189
+ "smoothstep",
190
+ "ease_in_out",
191
+ "ease_out",
192
+ "ease_in",
193
+ "load_font",
194
+ "outline",
195
+ "shadow",
196
+ "wrap",
197
+ "render_multiline",
198
+ "fit",
199
+ "Signal",
200
+ "SignalBus",
201
+ "signal",
202
+ "Renderable",
203
+ "UIElement",
204
+ "Drawable",
205
+ "Updateable",
206
+ "Clickable",
207
+ "Widget",
208
+ ]
209
+
210
+
211
+ def __getattr__(name: str) -> Any:
212
+ try:
213
+ sub = _LAZY_ATTRS[name]
214
+ except KeyError:
215
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
216
+ module = importlib.import_module(f"{__name__}.{sub}")
217
+ value = getattr(module, name)
218
+ globals()[name] = value
219
+ return value
220
+
221
+
222
+ def __dir__() -> list[str]:
223
+ return sorted(__all__)
@@ -196,7 +196,7 @@ class AnimationSheet:
196
196
  ``2.0`` doubles dimensions, ``0.5`` halves them.
197
197
  """
198
198
 
199
- __slots__ = ("_surface", "_rows", "_cols", "_cell_w", "_cell_h", "_cache", "_frames", "_scale", "_orig_cell_w", "_orig_cell_h")
199
+ __slots__ = ("_surface", "_rows", "_cols", "_cell_w", "_cell_h", "_cache", "_frames", "_scale", "_orig_cell_w", "_orig_cell_h", "_start_row", "_start_col", "_sheet_rows", "_sheet_cols")
200
200
 
201
201
  def __init__(
202
202
  self,
@@ -205,11 +205,35 @@ class AnimationSheet:
205
205
  cols: int = 1,
206
206
  pattern: str | None = None,
207
207
  scale: float = 1.0,
208
+ start_row: int = 0,
209
+ start_col: int = 0,
210
+ sheet_rows: int | None = None,
211
+ sheet_cols: int | None = None,
208
212
  ) -> None:
209
213
  if not isinstance(rows, int) or not isinstance(cols, int) or isinstance(rows, bool) or isinstance(cols, bool):
210
214
  raise TypeError("rows and cols must be ints")
211
215
  if rows < 1 or cols < 1:
212
216
  raise ValueError(f"rows and cols must be >= 1, got rows={rows}, cols={cols}")
217
+ if not isinstance(start_row, int) or not isinstance(start_col, int) or isinstance(start_row, bool) or isinstance(start_col, bool):
218
+ raise TypeError("start_row and start_col must be ints")
219
+ if start_row < 0 or start_col < 0:
220
+ raise ValueError(f"start_row and start_col must be >= 0, got start_row={start_row}, start_col={start_col}")
221
+ if sheet_rows is not None:
222
+ if not isinstance(sheet_rows, int) or isinstance(sheet_rows, bool):
223
+ raise TypeError("sheet_rows must be int or None")
224
+ if sheet_rows < 1:
225
+ raise ValueError(f"sheet_rows must be >= 1, got {sheet_rows}")
226
+ if sheet_cols is not None:
227
+ if not isinstance(sheet_cols, int) or isinstance(sheet_cols, bool):
228
+ raise TypeError("sheet_cols must be int or None")
229
+ if sheet_cols < 1:
230
+ raise ValueError(f"sheet_cols must be >= 1, got {sheet_cols}")
231
+ full_rows = sheet_rows if sheet_rows is not None else rows
232
+ full_cols = sheet_cols if sheet_cols is not None else cols
233
+ if start_row + rows > full_rows or start_col + cols > full_cols:
234
+ raise ValueError(
235
+ f"window rows={rows} cols={cols} at start_row={start_row} start_col={start_col} exceeds sheet {full_rows}x{full_cols}"
236
+ )
213
237
  if pattern is not None and not isinstance(pattern, str):
214
238
  raise TypeError("pattern must be str or None")
215
239
  if not isinstance(scale, (int, float)) or isinstance(scale, bool):
@@ -223,9 +247,9 @@ class AnimationSheet:
223
247
  raise ValueError("pattern is only valid when image is a directory path")
224
248
  surface = image
225
249
  width, height = surface.get_size()
226
- cell_w, cell_h = width // cols, height // rows
250
+ cell_w, cell_h = width // full_cols, height // full_rows
227
251
  if cell_w < 1 or cell_h < 1:
228
- raise ValueError(f"Sheet {width}x{height} cannot fit a {rows}x{cols} grid")
252
+ raise ValueError(f"Sheet {width}x{height} cannot fit a {full_rows}x{full_cols} grid")
229
253
  self._surface: Surface | None = surface
230
254
  self._rows = rows
231
255
  self._cols = cols
@@ -243,6 +267,10 @@ class AnimationSheet:
243
267
  self._cell_w, self._cell_h = scaled_dummy.get_size()
244
268
  self._cache: dict[int, Surface] = {}
245
269
  self._frames: list[Surface] | None = None
270
+ self._start_row = start_row
271
+ self._start_col = start_col
272
+ self._sheet_rows = full_rows
273
+ self._sheet_cols = full_cols
246
274
  return
247
275
 
248
276
  path = Path(image)
@@ -250,8 +278,14 @@ class AnimationSheet:
250
278
  if path.is_dir():
251
279
  if rows != 1 or cols != 1:
252
280
  raise ValueError(f"rows and cols are ignored in directory mode (got rows={rows}, cols={cols}); use pattern/scale instead")
281
+ if start_row != 0 or start_col != 0 or sheet_rows is not None or sheet_cols is not None:
282
+ raise ValueError("start_row/start_col/sheet_rows/sheet_cols are ignored in directory mode")
253
283
  self._scale = scale_f
254
284
  self._init_from_directory(path, pattern)
285
+ self._start_row = 0
286
+ self._start_col = 0
287
+ self._sheet_rows = 1
288
+ self._sheet_cols = self._cols
255
289
  return
256
290
 
257
291
  if pattern is not None:
@@ -268,9 +302,9 @@ class AnimationSheet:
268
302
  pass
269
303
 
270
304
  width, height = surface.get_size()
271
- cell_w, cell_h = width // cols, height // rows
305
+ cell_w, cell_h = width // full_cols, height // full_rows
272
306
  if cell_w < 1 or cell_h < 1:
273
- raise ValueError(f"Sheet {width}x{height} cannot fit a {rows}x{cols} grid")
307
+ raise ValueError(f"Sheet {width}x{height} cannot fit a {full_rows}x{full_cols} grid")
274
308
 
275
309
  self._surface = surface
276
310
  self._rows = rows
@@ -287,6 +321,10 @@ class AnimationSheet:
287
321
  self._cell_w, self._cell_h = scaled_dummy.get_size()
288
322
  self._cache: dict[int, Surface] = {}
289
323
  self._frames = None
324
+ self._start_row = start_row
325
+ self._start_col = start_col
326
+ self._sheet_rows = full_rows
327
+ self._sheet_cols = full_cols
290
328
 
291
329
  def _init_from_directory(self, dir_path: Path, pattern: str | None) -> None:
292
330
  files = _collect_image_files(dir_path, pattern)
@@ -338,13 +376,18 @@ class AnimationSheet:
338
376
 
339
377
  @classmethod
340
378
  def load(
341
- cls, path: PathLike, rows: int = 1, cols: int = 1, pattern: str | None = None, scale: float = 1.0
379
+ cls,
380
+ path: PathLike,
381
+ rows: int = 1,
382
+ cols: int = 1,
383
+ pattern: str | None = None,
384
+ scale: float = 1.0,
385
+ start_row: int = 0,
386
+ start_col: int = 0,
387
+ sheet_rows: int | None = None,
388
+ sheet_cols: int | None = None,
342
389
  ) -> "AnimationSheet":
343
- """Load a sheet from a file or directory.
344
-
345
- See :class:`AnimationSheet` for ``pattern`` and ``scale`` semantics.
346
- """
347
- return cls(path, rows, cols, pattern=pattern, scale=scale)
390
+ return cls(path, rows, cols, pattern=pattern, scale=scale, start_row=start_row, start_col=start_col, sheet_rows=sheet_rows, sheet_cols=sheet_cols)
348
391
 
349
392
  @classmethod
350
393
  def from_directory(cls, directory: PathLike, pattern: str | None = None, scale: float = 1.0) -> "AnimationSheet":
@@ -377,21 +420,35 @@ class AnimationSheet:
377
420
  def scale(self) -> float:
378
421
  return self._scale
379
422
 
423
+ @property
424
+ def start_row(self) -> int:
425
+ return self._start_row
426
+
427
+ @property
428
+ def start_col(self) -> int:
429
+ return self._start_col
430
+
431
+ @property
432
+ def sheet_rows(self) -> int:
433
+ return self._sheet_rows
434
+
435
+ @property
436
+ def sheet_cols(self) -> int:
437
+ return self._sheet_cols
438
+
380
439
  def get_frame(self, index: int) -> Surface:
381
440
  if index < 0 or index >= self.frame_count:
382
441
  raise IndexError(f"Frame index {index} out of range for {self.frame_count} frames")
383
442
  if self._frames is not None:
384
- # Directory mode: frames already loaded and cached (scaled if needed).
385
443
  return self._cache[index]
386
444
  cached = self._cache.get(index)
387
445
  if cached is None:
388
- col = index % self._cols
389
- row = index // self._cols
446
+ col = self._start_col + (index % self._cols)
447
+ row = self._start_row + (index // self._cols)
390
448
  if self._scale == 1.0:
391
449
  src = Rect(col * self._cell_w, row * self._cell_h, self._cell_w, self._cell_h)
392
450
  cached = self._surface.subsurface(src).copy() # type: ignore[union-attr]
393
451
  else:
394
- # Use stored original cell size to avoid rounding drift.
395
452
  src = Rect(col * self._orig_cell_w, row * self._orig_cell_h, self._orig_cell_w, self._orig_cell_h)
396
453
  tmp = self._surface.subsurface(src).copy() # type: ignore[union-attr]
397
454
  cached = _scale_surface(tmp, self._scale)
@@ -1,20 +1,5 @@
1
1
  """
2
- Pygkit Inventory System - Production-grade inventory management for Pygame.
3
-
4
- A complete, generic, and optimized inventory system with:
5
- - Data-driven item definitions
6
- - Grid-based slot management
7
- - Drag-and-drop interactions
8
- - Tooltips and visual feedback
9
- - Asset caching for performance
10
- - Event-driven architecture
11
- - Save/load serialization support
12
-
13
- Modules:
14
- data: Type definitions and data structures
15
- core: Inventory logic and state management
16
- assets: Asset caching and procedural generation
17
- ui: UI rendering and user interaction
2
+ Grid inventory: data definitions, slot logic, icon assets, widgets.
18
3
 
19
4
  Usage:
20
5
  from pygkit.inventory import Inventory, InventoryWidget, ItemData
@@ -1,8 +1,5 @@
1
1
  """
2
- Asset caching and procedural generation for the Inventory System.
3
-
4
- Provides optimized caching of static UI elements and procedural
5
- generation of item icons to avoid runtime overhead.
2
+ Slot surface cache and item icon generation.
6
3
  """
7
4
 
8
5
  from __future__ import annotations
@@ -19,8 +19,7 @@ from .data import (
19
19
 
20
20
 
21
21
  class Inventory:
22
- """
23
- Generic inventory container with full item management capabilities.
22
+ """Grid slot container with stacking, weight, events, serialization.
24
23
 
25
24
  Features:
26
25
  - Grid-based slot system with configurable size
@@ -14,8 +14,7 @@ from pygame.typing import ColorLike
14
14
 
15
15
 
16
16
  class ItemData(TypedDict, total=False):
17
- """
18
- Flexible item definition with required core fields and optional metadata.
17
+ """Item definition with required core fields and optional metadata.
19
18
 
20
19
  Required:
21
20
  id: Unique string identifier (e.g., "potion_health_small")
@@ -18,7 +18,7 @@ the scene) with game-agnostic lights. Straight imports, no game wiring::
18
18
  lightmap.apply(scene, blend=RGBA_MULT)
19
19
  """
20
20
 
21
- from pygkit.lighting.blend import (
21
+ from .blend import (
22
22
  LIGHT_ADD,
23
23
  RGB_ADD,
24
24
  RGB_MAX,
@@ -32,9 +32,9 @@ from pygkit.lighting.blend import (
32
32
  RGBA_SUB,
33
33
  SCENE_MULT,
34
34
  )
35
- from pygkit.lighting.lightmap import LightMap
36
- from pygkit.lighting.lights import DynamicLight, PointLight, Spotlight
37
- from pygkit.lighting.sprites import (
35
+ from .lightmap import LightMap
36
+ from .lights import DynamicLight, PointLight, Spotlight
37
+ from .sprites import (
38
38
  clear_sprite_cache,
39
39
  dynamic_glow_sprite,
40
40
  glow_sprite,
@@ -23,7 +23,7 @@ from __future__ import annotations
23
23
  import pygame
24
24
  from pygame import Surface
25
25
 
26
- from pygkit.lighting.blend import LIGHT_ADD, SCENE_MULT
26
+ from .blend import LIGHT_ADD, SCENE_MULT
27
27
 
28
28
  __all__ = ["LightMap"]
29
29
 
@@ -17,9 +17,9 @@ import random
17
17
  import pygame
18
18
  from pygame import Surface
19
19
 
20
- from pygkit.lighting.blend import LIGHT_ADD
21
- from pygkit.lighting.lightmap import LightMap
22
- from pygkit.lighting.sprites import dynamic_glow_sprite, glow_sprite, spotlight_sprite
20
+ from .blend import LIGHT_ADD
21
+ from .lightmap import LightMap
22
+ from .sprites import dynamic_glow_sprite, glow_sprite, spotlight_sprite
23
23
 
24
24
  __all__ = ["PointLight", "Spotlight", "DynamicLight"]
25
25
 
@@ -0,0 +1,49 @@
1
+ from typing import Any, Protocol, runtime_checkable
2
+
3
+ from pygame import Surface
4
+
5
+ from .signals import Signal
6
+
7
+
8
+ class Renderable(Protocol):
9
+ def render(self, screen: Surface, pos_offset: tuple[int, int] = (0, 0)): ...
10
+
11
+ def update(self): ...
12
+
13
+
14
+ class UIElement(Protocol):
15
+ def render(self, screen: Surface): ...
16
+
17
+ def update(self): ...
18
+
19
+ @property
20
+ def size(self) -> tuple[int, int]: ...
21
+
22
+ @property
23
+ def pos(self) -> tuple[int, int]: ...
24
+
25
+
26
+ class Drawable(Protocol):
27
+ def draw(self, surface: Surface): ...
28
+
29
+
30
+ class Updateable(Protocol):
31
+ def update(self): ...
32
+
33
+
34
+ @runtime_checkable
35
+ class Widget(Renderable, Protocol):
36
+ @property
37
+ def size(self) -> tuple[int, int]: ...
38
+
39
+
40
+ class Clickable(Widget, Protocol):
41
+ # contains/press/activate take screen coords; widgets track origin at render.
42
+
43
+ on_press: Signal[Any]
44
+
45
+ def contains(self, pos: tuple[int, int]) -> bool: ...
46
+
47
+ def press(self, pos: tuple[int, int]) -> bool: ...
48
+
49
+ def activate(self, pos: tuple[int, int]) -> bool: ...
@@ -1,7 +1,10 @@
1
+ from .anchors import ANCHORS, anchor_point, column, grid, place, row
1
2
  from .base import UIBase, UIOptions, BoxModel, BoxModelResult, generate_box_model
3
+ from .button import Button, ButtonStyle
2
4
  from .container import Container
3
5
  from .cooldown import CooldownOverlay
4
6
  from .dialog import DialogBox, DialogConfig, DialogColors, DialogLine, TypewriterSpeed
7
+ from .menubar import Menubar
5
8
  from .progressbar import ProgressBarUI
6
9
 
7
10
  __all__ = [
@@ -18,4 +21,13 @@ __all__ = [
18
21
  "DialogColors",
19
22
  "DialogLine",
20
23
  "TypewriterSpeed",
24
+ "Button",
25
+ "ButtonStyle",
26
+ "Menubar",
27
+ "ANCHORS",
28
+ "anchor_point",
29
+ "place",
30
+ "row",
31
+ "column",
32
+ "grid",
21
33
  ]