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.
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/PKG-INFO +1 -1
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/pyproject.toml +1 -1
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/__init__.py +2 -0
- mini_arcade_core-0.9.5/src/mini_arcade_core/autoreg.py +39 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/registry.py +51 -1
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/ui/menu.py +35 -13
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/LICENSE +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/README.md +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/backend.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/boundaries2d.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/collision2d.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/entity.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/game.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/geometry2d.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/keymaps/__init__.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/keymaps/sdl.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/keys.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/kinematics2d.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/physics2d.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/scene.py +0 -0
- {mini_arcade_core-0.9.3 → mini_arcade_core-0.9.5}/src/mini_arcade_core/ui/__init__.py +0 -0
|
@@ -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.
|
|
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)
|
|
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(
|
|
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 +=
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|