mini-arcade-native-backend 0.4.6__cp310-cp310-win_amd64.whl → 0.5.3__cp310-cp310-win_amd64.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.
@@ -6,8 +6,9 @@ from __future__ import annotations
6
6
 
7
7
  import os
8
8
  import sys
9
+ from dataclasses import dataclass
9
10
  from pathlib import Path
10
- from typing import Union
11
+ from typing import Optional, Union
11
12
 
12
13
  # --- 1) Make sure Windows can find SDL2.dll when using vcpkg ------------------
13
14
 
@@ -37,13 +38,28 @@ if sys.platform == "win32":
37
38
 
38
39
  # Justification: Need to import core after setting DLL path on Windows
39
40
  # pylint: disable=wrong-import-position
40
- from mini_arcade_core.backend import Backend, Event, EventType
41
- from mini_arcade_core.keymaps.sdl import SDL_KEYCODE_TO_KEY
41
+ # Justification: When mini-arcade-core is installed in editable mode, import-error
42
+ # false positive can occur.
43
+ # pylint: disable=import-error
44
+ from mini_arcade_core.backend import ( # pyright: ignore[reportMissingImports]
45
+ Backend,
46
+ WindowSettings,
47
+ )
48
+ from mini_arcade_core.backend.events import ( # pyright: ignore[reportMissingImports]
49
+ Event,
50
+ EventType,
51
+ )
52
+ from mini_arcade_core.backend.sdl_map import ( # pyright: ignore[reportMissingImports]
53
+ SDL_KEYCODE_TO_KEY,
54
+ )
42
55
 
43
56
  # Justification: Importing the native extension module
44
57
  # pylint: disable=import-self,no-name-in-module
45
58
  from . import _native as native
46
59
 
60
+ # pylint: enable=import-error
61
+
62
+
47
63
  # --- 2) Now import core + define NativeBackend as before ---
48
64
 
49
65
 
@@ -65,23 +81,49 @@ _NATIVE_TO_CORE = {
65
81
  }
66
82
 
67
83
 
84
+ @dataclass
85
+ class BackendSettings:
86
+ """
87
+ Settings for the NativeBackend.
88
+
89
+ :ivar font_path (Optional[str]): Optional path to a TTF font file to load.
90
+ :ivar font_size (int): Font size in points to use when loading the font.
91
+ :ivar sounds (Optional[dict[str, str]]): Optional dictionary mapping sound IDs to file paths.
92
+ """
93
+
94
+ font_path: Optional[str] = None
95
+ font_size: int = 24
96
+ sounds: Optional[dict[str, str]] = None # sound_id -> path
97
+
98
+
99
+ # TODO: Refactor backend interface into smaller protocols?
100
+ # Justification: Many public methods needed for backend interface
101
+ # pylint: disable=too-many-public-methods,too-many-instance-attributes
68
102
  class NativeBackend(Backend):
69
103
  """Adapter that makes the C++ Engine usable as a mini-arcade backend."""
70
104
 
71
- def __init__(self, font_path: str | None = None, font_size: int = 24):
105
+ def __init__(self, backend_settings: BackendSettings | None = None):
72
106
  """
73
- :param font_path: Optional path to a TTF font file to load.
74
- :type font_path: str | None
75
-
76
- :param font_size: Font size in points to use when loading the font.
77
- :type font_size: int
107
+ :param backend_settings: Optional settings for the backend.
108
+ :type backend_settings: BackendSettings | None
78
109
  """
79
110
  self._engine = native.Engine()
80
- self._font_path = font_path
81
- self._font_size = font_size
111
+
112
+ self._font_path = (
113
+ backend_settings.font_path if backend_settings else None
114
+ )
115
+ self._font_size = (
116
+ backend_settings.font_size if backend_settings else 24
117
+ )
82
118
  self._default_font_id: int | None = None
83
119
  self._fonts_by_size: dict[int, int] = {}
84
120
 
121
+ self._sounds = backend_settings.sounds if backend_settings else None
122
+
123
+ self._vp_offset_x = 0
124
+ self._vp_offset_y = 0
125
+ self._vp_scale = 1.0
126
+
85
127
  def _get_font_id(self, font_size: int | None) -> int:
86
128
  # No font loaded -> keep current “no-op” behavior
87
129
  if self._font_path is None:
@@ -108,20 +150,15 @@ class NativeBackend(Backend):
108
150
  self._fonts_by_size[font_size] = font_id
109
151
  return font_id
110
152
 
111
- def init(self, width: int, height: int, title: str):
153
+ def init(self, window_settings: WindowSettings):
112
154
  """
113
155
  Initialize the backend with a window of given width, height, and title.
114
156
 
115
- :param width: Width of the window in pixels.
116
- :type width: int
117
-
118
- :param height: Height of the window in pixels.
119
- :type height: int
120
-
121
- :param title: Title of the window.
122
- :type title: str
157
+ :param window_settings: Settings for the backend window.
158
+ :type window_settings: WindowSettings
123
159
  """
124
- self._engine.init(width, height, title)
160
+ title = ""
161
+ self._engine.init(window_settings.width, window_settings.height, title)
125
162
 
126
163
  # Load font if provided
127
164
  if self._font_path is not None:
@@ -130,6 +167,20 @@ class NativeBackend(Backend):
130
167
  )
131
168
  self._fonts_by_size[self._font_size] = self._default_font_id
132
169
 
170
+ # Load sounds if provided
171
+ if self._sounds is not None:
172
+ for sound_id, path in self._sounds.items():
173
+ self.load_sound(sound_id, path)
174
+
175
+ def set_window_title(self, title: str):
176
+ """
177
+ Set the window title.
178
+
179
+ :param title: Title of the window.
180
+ :type title: str
181
+ """
182
+ self._engine.set_window_title(title)
183
+
133
184
  def set_clear_color(self, r: int, g: int, b: int):
134
185
  """
135
186
  Set the background/clear color used by begin_frame.
@@ -303,7 +354,12 @@ class NativeBackend(Backend):
303
354
  :type color: tuple[int, ...]
304
355
  """
305
356
  r, g, b, a = self._get_color_values(color)
306
- self._engine.draw_rect(x, y, w, h, r, g, b, a)
357
+ sx = int(round(self._vp_offset_x + x * self._vp_scale)) # top-left x
358
+ sy = int(round(self._vp_offset_y + y * self._vp_scale)) # top-left y
359
+ sw = int(round(w * self._vp_scale)) # width
360
+ sh = int(round(h * self._vp_scale)) # height
361
+ self._engine.draw_rect(sx, sy, sw, sh, r, g, b, a)
362
+ # self._engine.draw_rect(x, y, w, h, r, g, b, a)
307
363
 
308
364
  def draw_text(
309
365
  self,
@@ -331,9 +387,22 @@ class NativeBackend(Backend):
331
387
  """
332
388
  r, g, b, a = self._get_color_values(color)
333
389
  font_id = self._get_font_id(font_size)
390
+ sx = int(round(self._vp_offset_x + x * self._vp_scale))
391
+ sy = int(round(self._vp_offset_y + y * self._vp_scale))
392
+
393
+ # optional but recommended: scale font size too
394
+ if font_size is not None:
395
+ scaled = max(8, int(round(font_size * self._vp_scale)))
396
+ else:
397
+ scaled = None
398
+
399
+ font_id = self._get_font_id(scaled)
334
400
  self._engine.draw_text(
335
- text, x, y, int(r), int(g), int(b), int(a), font_id
401
+ text, sx, sy, int(r), int(g), int(b), int(a), font_id
336
402
  )
403
+ # self._engine.draw_text(
404
+ # text, x, y, int(r), int(g), int(b), int(a), font_id
405
+ # )
337
406
 
338
407
  # pylint: enable=too-many-arguments,too-many-positional-arguments
339
408
 
@@ -363,3 +432,73 @@ class NativeBackend(Backend):
363
432
  font_id = self._get_font_id(font_size)
364
433
  w, h = self._engine.measure_text(text, font_id)
365
434
  return int(w), int(h)
435
+
436
+ def init_audio(
437
+ self, frequency: int = 44100, channels: int = 2, chunk_size: int = 2048
438
+ ):
439
+ """Initialize SDL_mixer audio."""
440
+ self._engine.init_audio(int(frequency), int(channels), int(chunk_size))
441
+
442
+ def shutdown_audio(self):
443
+ """Shutdown SDL_mixer audio and free loaded sounds."""
444
+ self._engine.shutdown_audio()
445
+
446
+ def load_sound(self, sound_id: str, path: str):
447
+ """
448
+ Load a WAV sound and store it by ID.
449
+ Example: backend.load_sound("hit", "assets/sfx/hit.wav")
450
+ """
451
+ if not sound_id:
452
+ raise ValueError("sound_id cannot be empty")
453
+
454
+ p = Path(path)
455
+ if not p.exists():
456
+ raise FileNotFoundError(f"Sound file not found: {p}")
457
+
458
+ self._engine.load_sound(sound_id, str(p))
459
+
460
+ def play_sound(self, sound_id: str, loops: int = 0):
461
+ """
462
+ Play a loaded sound.
463
+ loops=0 => play once
464
+ loops=-1 => infinite loop
465
+ loops=1 => play twice (SDL convention)
466
+ """
467
+ self._engine.play_sound(sound_id, int(loops))
468
+
469
+ def set_master_volume(self, volume: int):
470
+ """
471
+ Master volume: 0..128
472
+ """
473
+ self._engine.set_master_volume(int(volume))
474
+
475
+ def set_sound_volume(self, sound_id: str, volume: int):
476
+ """
477
+ Per-sound volume: 0..128
478
+ """
479
+ self._engine.set_sound_volume(sound_id, int(volume))
480
+
481
+ def stop_all_sounds(self):
482
+ """Stop all channels."""
483
+ self._engine.stop_all_sounds()
484
+
485
+ def set_viewport_transform(
486
+ self, offset_x: int, offset_y: int, scale: float
487
+ ) -> None:
488
+ self._vp_offset_x = int(offset_x)
489
+ self._vp_offset_y = int(offset_y)
490
+ self._vp_scale = float(scale)
491
+
492
+ def clear_viewport_transform(self) -> None:
493
+ self._vp_offset_x = 0
494
+ self._vp_offset_y = 0
495
+ self._vp_scale = 1.0
496
+
497
+ def resize_window(self, width: int, height: int) -> None:
498
+ self._engine.resize_window(int(width), int(height))
499
+
500
+ def set_clip_rect(self, x: int, y: int, w: int, h: int) -> None:
501
+ self._engine.set_clip_rect(int(x), int(y), int(w), int(h))
502
+
503
+ def clear_clip_rect(self) -> None:
504
+ self._engine.clear_clip_rect()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mini-arcade-native-backend
3
- Version: 0.4.6
3
+ Version: 0.5.3
4
4
  Summary: Native SDL2 backend for mini-arcade-core using SDL2 + pybind11.
5
5
  Author-Email: Santiago Rincon <rincores@gmail.com>
6
6
  License: Copyright (c) 2025 Santiago Rincón
@@ -24,7 +24,7 @@ License: Copyright (c) 2025 Santiago Rincón
24
24
  SOFTWARE.
25
25
 
26
26
  Requires-Python: <3.12,>=3.9
27
- Requires-Dist: mini-arcade-core~=0.10
27
+ Requires-Dist: mini-arcade-core~=1.0
28
28
  Provides-Extra: dev
29
29
  Requires-Dist: pytest~=8.3; extra == "dev"
30
30
  Requires-Dist: pytest-cov~=6.0; extra == "dev"
@@ -0,0 +1,6 @@
1
+ mini_arcade_native_backend/__init__.py,sha256=eguv2ESm07S1h8beSRfKKhMFBywfpsUlo-3forhhCoc,16809
2
+ mini_arcade_native_backend/_native.cp310-win_amd64.pyd,sha256=RdVjaGR589_NHqKLYkwZvhO0FU7iME3OB7dH1o_Uckk,227328
3
+ mini_arcade_native_backend-0.5.3.dist-info/METADATA,sha256=Br3ckI1QZuE2tgZ7eKIgkQf_g8z-b69kdcQIe8k4SSs,10517
4
+ mini_arcade_native_backend-0.5.3.dist-info/WHEEL,sha256=hrGeChGtn46HBGmzasO9QQDSLelRN-tUarBSv4gFcsI,106
5
+ mini_arcade_native_backend-0.5.3.dist-info/licenses/LICENSE,sha256=cZRgTdRJ3YASekMxkGAvylB2nROh4ov228DxAogK3yY,1115
6
+ mini_arcade_native_backend-0.5.3.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- mini_arcade_native_backend/__init__.py,sha256=dUcIJJBR6TLFvhjQdNoA-CoT3l6nntrxzhwYvGOkg40,12005
2
- mini_arcade_native_backend/_native.cp310-win_amd64.pyd,sha256=wGkC8dY6iEGXZOgFdSesFiRAPnZOQ1LX7oW18cZXbPA,212992
3
- mini_arcade_native_backend-0.4.6.dist-info/METADATA,sha256=rBo0xnG-rqR-gnANDoASoZq5rsZzaWv6SDoBYACXB9Q,10518
4
- mini_arcade_native_backend-0.4.6.dist-info/WHEEL,sha256=hrGeChGtn46HBGmzasO9QQDSLelRN-tUarBSv4gFcsI,106
5
- mini_arcade_native_backend-0.4.6.dist-info/licenses/LICENSE,sha256=cZRgTdRJ3YASekMxkGAvylB2nROh4ov228DxAogK3yY,1115
6
- mini_arcade_native_backend-0.4.6.dist-info/RECORD,,