mini-arcade-core 0.9.3__tar.gz → 0.9.5__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 (21) hide show
  1. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/PKG-INFO +1 -1
  2. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/pyproject.toml +1 -1
  3. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/__init__.py +2 -0
  4. mini_arcade_core-0.9.5/src/mini_arcade_core/autoreg.py +39 -0
  5. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/registry.py +51 -1
  6. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/ui/menu.py +35 -13
  7. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/LICENSE +0 -0
  8. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/README.md +0 -0
  9. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/backend.py +0 -0
  10. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/boundaries2d.py +0 -0
  11. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/collision2d.py +0 -0
  12. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/entity.py +0 -0
  13. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/game.py +0 -0
  14. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/geometry2d.py +0 -0
  15. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/keymaps/__init__.py +0 -0
  16. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/keymaps/sdl.py +0 -0
  17. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/keys.py +0 -0
  18. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/kinematics2d.py +0 -0
  19. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/physics2d.py +0 -0
  20. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/scene.py +0 -0
  21. {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/ui/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-arcade-core
3
- Version: 0.9.3
3
+ Version: 0.9.5
4
4
  Summary: Tiny scene-based game loop core for small arcade games.
5
5
  License: Copyright (c) 2025 Santiago Rincón
6
6
 
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "mini-arcade-core"
7
- version = "0.9.3"
7
+ version = "0.9.5"
8
8
  description = "Tiny scene-based game loop core for small arcade games."
9
9
  authors = [
10
10
  { name = "Santiago Rincon", email = "rincores@gmail.com" },
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
  import logging
9
9
  from importlib.metadata import PackageNotFoundError, version
10
10
 
11
+ from .autoreg import register_scene
11
12
  from .backend import Backend, Event, EventType
12
13
  from .boundaries2d import (
13
14
  RectKinematic,
@@ -78,6 +79,7 @@ __all__ = [
78
79
  "Key",
79
80
  "keymap",
80
81
  "SceneRegistry",
82
+ "register_scene",
81
83
  ]
82
84
 
83
85
  PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
@@ -0,0 +1,39 @@
1
+ """
2
+ Autoregistration utilities for mini arcade core.
3
+ Allows automatic registration of Scene classes via decorators.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING, Dict, Type
9
+
10
+ if TYPE_CHECKING:
11
+ from .scene import Scene
12
+
13
+ _AUTO: Dict[str, Type["Scene"]] = {}
14
+
15
+
16
+ def register_scene(scene_id: str):
17
+ """Decorator to mark and register a Scene class under an id."""
18
+
19
+ def deco(cls: Type["Scene"]):
20
+ _AUTO[scene_id] = cls
21
+ setattr(cls, "__scene_id__", scene_id)
22
+ return cls
23
+
24
+ return deco
25
+
26
+
27
+ def snapshot() -> Dict[str, Type["Scene"]]:
28
+ """
29
+ Copy of current catalog (useful for tests).
30
+
31
+ :return: A copy of the current scene catalog.
32
+ :rtype: Dict[str, Type["Scene"]]
33
+ """
34
+ return dict(_AUTO)
35
+
36
+
37
+ def clear():
38
+ """Clear the current catalog (useful for tests)."""
39
+ _AUTO.clear()
@@ -5,9 +5,13 @@ Allows registering and creating scenes by string IDs.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import importlib
9
+ import pkgutil
8
10
  from dataclasses import dataclass
9
11
  from typing import TYPE_CHECKING, Dict, Protocol
10
12
 
13
+ from .autoreg import snapshot
14
+
11
15
  if TYPE_CHECKING:
12
16
  from mini_arcade_core.game import Game
13
17
  from mini_arcade_core.scene import Scene
@@ -29,7 +33,7 @@ class SceneRegistry:
29
33
 
30
34
  _factories: Dict[str, SceneFactory]
31
35
 
32
- def register(self, scene_id: str, factory: SceneFactory) -> None:
36
+ def register(self, scene_id: str, factory: SceneFactory):
33
37
  """
34
38
  Register a scene factory under a given scene ID.
35
39
 
@@ -41,6 +45,22 @@ class SceneRegistry:
41
45
  """
42
46
  self._factories[scene_id] = factory
43
47
 
48
+ def register_cls(self, scene_id: str, scene_cls: type["Scene"]):
49
+ """
50
+ Register a Scene class under a given scene ID.
51
+
52
+ :param scene_id: The string ID for the scene.
53
+ :type scene_id: str
54
+
55
+ :param scene_cls: The Scene class to register.
56
+ :type scene_cls: type["Scene"]
57
+ """
58
+
59
+ def return_factory(game: "Game") -> "Scene":
60
+ return scene_cls(game)
61
+
62
+ self.register(scene_id, return_factory)
63
+
44
64
  def create(self, scene_id: str, game: "Game") -> "Scene":
45
65
  """
46
66
  Create a scene instance using the registered factory for the given scene ID.
@@ -60,3 +80,33 @@ class SceneRegistry:
60
80
  return self._factories[scene_id](game)
61
81
  except KeyError as e:
62
82
  raise KeyError(f"Unknown scene_id={scene_id!r}") from e
83
+
84
+ def load_catalog(self, catalog: Dict[str, type["Scene"]]):
85
+ """
86
+ Load a catalog of Scene classes into the registry.
87
+
88
+ :param catalog: A dictionary mapping scene IDs to Scene classes.
89
+ :type catalog: Dict[str, type["Scene"]]
90
+ """
91
+ for scene_id, cls in catalog.items():
92
+ self.register_cls(scene_id, cls)
93
+
94
+ def discover(self, package: str) -> "SceneRegistry":
95
+ """
96
+ Import all modules in a package so @scene decorators run.
97
+
98
+ :param package: The package name to scan for scene modules.
99
+ :type package: str
100
+
101
+ :return: The SceneRegistry instance (for chaining).
102
+ :rtype: SceneRegistry
103
+ """
104
+ pkg = importlib.import_module(package)
105
+ if not hasattr(pkg, "__path__"):
106
+ return self # not a package
107
+
108
+ for mod in pkgutil.walk_packages(pkg.__path__, pkg.__name__ + "."):
109
+ importlib.import_module(mod.name)
110
+
111
+ self.load_catalog(snapshot())
112
+ return self
@@ -46,6 +46,7 @@ class MenuStyle:
46
46
  line_height: int = 28
47
47
  title_color: Color = (255, 255, 255)
48
48
  title_spacing: int = 18
49
+ title_margin_bottom: int = 20
49
50
 
50
51
  # Scene background (solid)
51
52
  background_color: Color | None = None # e.g. BACKGROUND
@@ -75,6 +76,11 @@ class MenuStyle:
75
76
  hint_color: Color = (200, 200, 200)
76
77
  hint_margin_bottom: int = 50
77
78
 
79
+ # Font sizes (not used directly here, but for reference)
80
+ title_font_size = 44
81
+ hint_font_size = 14
82
+ item_font_size = 24
83
+
78
84
 
79
85
  # pylint: enable=too-many-instance-attributes
80
86
 
@@ -165,13 +171,15 @@ class Menu:
165
171
  title_h = 0
166
172
 
167
173
  if self.title:
168
- tw, th = surface.measure_text(self.title)
174
+ tw, th = surface.measure_text(
175
+ self.title, self.style.title_font_size
176
+ )
169
177
  max_w = max(max_w, tw)
170
178
  title_h = th
171
179
 
172
180
  # Items
173
181
  for it in self.items:
174
- w, _ = surface.measure_text(it.label)
182
+ w, _ = surface.measure_text(it.label, self.style.item_font_size)
175
183
  max_w = max(max_w, w)
176
184
 
177
185
  items_h = len(self.items) * self.style.line_height
@@ -235,8 +243,13 @@ class Menu:
235
243
  cursor_y,
236
244
  self.title,
237
245
  color=self.style.title_color,
246
+ font_size=self.style.title_font_size,
247
+ )
248
+ cursor_y += (
249
+ title_h
250
+ + self.style.title_spacing
251
+ + self.style.title_margin_bottom
238
252
  )
239
- cursor_y += title_h + self.style.title_spacing
240
253
 
241
254
  if self.style.button_enabled:
242
255
  self._draw_buttons(surface, x_center, cursor_y)
@@ -251,11 +264,10 @@ class Menu:
251
264
  vh - self.style.hint_margin_bottom,
252
265
  self.style.hint,
253
266
  color=self.style.hint_color,
267
+ font_size=self.style.hint_font_size,
254
268
  )
255
269
 
256
- def _draw_text_items(
257
- self, surface: Backend, x_center: int, cursor_y: int
258
- ) -> None:
270
+ def _draw_text_items(self, surface: Backend, x_center: int, cursor_y: int):
259
271
  for i, item in enumerate(self.items):
260
272
  color = (
261
273
  self.style.selected
@@ -268,13 +280,12 @@ class Menu:
268
280
  cursor_y + i * self.style.line_height,
269
281
  item.label,
270
282
  color=color,
283
+ font_size=self.style.item_font_size,
271
284
  )
272
285
 
273
286
  # Justification: Local variables for layout calculations
274
287
  # pylint: disable=too-many-locals
275
- def _draw_buttons(
276
- self, surface: Backend, x_center: int, cursor_y: int
277
- ) -> None:
288
+ def _draw_buttons(self, surface: Backend, x_center: int, cursor_y: int):
278
289
  # Determine button width: fixed or auto-fit
279
290
  if self.style.button_width is not None:
280
291
  bw = self.style.button_width
@@ -354,10 +365,16 @@ class Menu:
354
365
 
355
366
  content_h = items_h
356
367
  if self.title:
357
- content_h += title_h + self.style.title_spacing
368
+ content_h += (
369
+ title_h
370
+ + self.style.title_spacing
371
+ + self.style.title_margin_bottom
372
+ )
358
373
 
359
374
  return max_w, content_h, title_h
360
375
 
376
+ # Justification: Many arguments for text drawing utility
377
+ # pylint: disable=too-many-arguments
361
378
  @staticmethod
362
379
  def _draw_text_center_x(
363
380
  surface: Backend,
@@ -366,6 +383,11 @@ class Menu:
366
383
  text: str,
367
384
  *,
368
385
  color: Color,
369
- ) -> None:
370
- w, _ = surface.measure_text(text)
371
- surface.draw_text(x_center - (w // 2), y, text, color=color)
386
+ font_size: int | None = None,
387
+ ):
388
+ w, _ = surface.measure_text(text, font_size=font_size)
389
+ surface.draw_text(
390
+ x_center - (w // 2), y, text, color=color, font_size=font_size
391
+ )
392
+
393
+ # pylint: enable=too-many-arguments