mini-arcade-native-backend 0.4.2__tar.gz → 0.4.4__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 (20) hide show
  1. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/CHANGELOG.md +20 -0
  2. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/PKG-INFO +1 -1
  3. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/cpp/bindings.cpp +9 -7
  4. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/cpp/engine.cpp +48 -29
  5. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/cpp/engine.h +7 -4
  6. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/pyproject.toml +1 -1
  7. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/src/mini_arcade_native_backend/__init__.py +59 -13
  8. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/tests/test_init.py +3 -3
  9. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/.github/workflows/ci.yml +0 -0
  10. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/.github/workflows/create-release-branch.yml +0 -0
  11. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/.github/workflows/release-finalize.yml +0 -0
  12. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/.github/workflows/release-publish.yml +0 -0
  13. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/.gitignore +0 -0
  14. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/.vscode/settings.json +0 -0
  15. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/CMakeLists.txt +0 -0
  16. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/LICENSE +0 -0
  17. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/README.md +0 -0
  18. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/examples/native_backend_demo.py +0 -0
  19. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/poetry.lock +0 -0
  20. {mini_arcade_native_backend-0.4.2 → mini_arcade_native_backend-0.4.4}/poetry.toml +0 -0
@@ -6,6 +6,26 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.4] - 2025-12-16
10
+
11
+ ### Added
12
+ - add measure_text method to Engine and expose it in NativeBackend
13
+ - add alpha handling methods and refactor color extraction in NativeBackend
14
+ - update draw_rect and draw_text methods to support alpha channel in color
15
+
16
+ ### Other
17
+ - Merge branch 'release/0.4' of https://github.com/alexsc6955/mini-arcade-native-backend into release/0.4
18
+ - Merge branch 'release/0.4' of https://github.com/alexsc6955/mini-arcade-native-backend into release/0.4
19
+
20
+ ## [0.4.3] - 2025-12-16
21
+
22
+ ### Added
23
+ - add alpha handling methods and refactor color extraction in NativeBackend
24
+ - update draw_rect and draw_text methods to support alpha channel in color
25
+
26
+ ### Other
27
+ - Merge branch 'release/0.4' of https://github.com/alexsc6955/mini-arcade-native-backend into release/0.4
28
+
9
29
  ## [0.4.2] - 2025-12-16
10
30
 
11
31
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mini-arcade-native-backend
3
- Version: 0.4.2
3
+ Version: 0.4.4
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
@@ -55,17 +55,12 @@ PYBIND11_MODULE(_native, m) {
55
55
  .def("draw_rect", &mini::Engine::draw_rect,
56
56
  py::arg("x"), py::arg("y"),
57
57
  py::arg("w"), py::arg("h"),
58
- py::arg("r"), py::arg("g"), py::arg("b"))
58
+ py::arg("r"), py::arg("g"), py::arg("b"), py::arg("a"))
59
59
 
60
60
  .def("draw_sprite", &mini::Engine::draw_sprite,
61
61
  py::arg("texture_id"), py::arg("x"), py::arg("y"),
62
62
  py::arg("w"), py::arg("h"))
63
63
 
64
- .def("draw_rect_rgba", &mini::Engine::draw_rect_rgba,
65
- py::arg("x"), py::arg("y"),
66
- py::arg("w"), py::arg("h"),
67
- py::arg("r"), py::arg("g"), py::arg("b"), py::arg("a"))
68
-
69
64
  .def("load_font", &mini::Engine::load_font,
70
65
  py::arg("path"), py::arg("pt_size"))
71
66
 
@@ -78,10 +73,17 @@ PYBIND11_MODULE(_native, m) {
78
73
  py::arg("r"),
79
74
  py::arg("g"),
80
75
  py::arg("b"),
76
+ py::arg("a"),
81
77
  py::arg("font_id") = -1
82
78
  )
83
79
  .def("poll_events", &mini::Engine::poll_events)
84
80
 
85
81
  .def("capture_frame", &mini::Engine::capture_frame,
86
- py::arg("path"));
82
+ py::arg("path"))
83
+ .def(
84
+ "measure_text",
85
+ &mini::Engine::measure_text,
86
+ py::arg("text"),
87
+ py::arg("font_id") = -1
88
+ );
87
89
  }
@@ -2,6 +2,8 @@
2
2
 
3
3
  #include <stdexcept>
4
4
  #include <iostream>
5
+ #include <utility>
6
+ #include <cstring>
5
7
 
6
8
  namespace mini {
7
9
 
@@ -11,7 +13,8 @@ namespace mini {
11
13
  initialized_(false),
12
14
  font_(nullptr),
13
15
  clear_color_{0, 0, 0, 255},
14
- default_font_id_(-1)
16
+ default_font_id_(-1),
17
+ default_alpha_(255)
15
18
  {
16
19
  }
17
20
 
@@ -36,6 +39,15 @@ namespace mini {
36
39
  SDL_Quit();
37
40
  initialized_ = false;
38
41
  }
42
+
43
+ for (TTF_Font* f : fonts_) {
44
+ if (f) TTF_CloseFont(f);
45
+ }
46
+ fonts_.clear();
47
+
48
+ TTF_Quit();
49
+ SDL_StopTextInput();
50
+
39
51
  }
40
52
 
41
53
  void Engine::init(int width, int height, const char* title)
@@ -133,7 +145,7 @@ namespace mini {
133
145
  SDL_RenderPresent(renderer_);
134
146
  }
135
147
 
136
- void Engine::draw_rect(int x, int y, int w, int h, int r, int g, int b)
148
+ void Engine::draw_rect(int x, int y, int w, int h, int r, int g, int b, int a)
137
149
  {
138
150
  if (!initialized_ || renderer_ == nullptr) {
139
151
  return;
@@ -147,12 +159,15 @@ namespace mini {
147
159
 
148
160
  SDL_Rect rect{ x, y, w, h };
149
161
 
162
+ // alpha or default alpha
163
+ a = (a < 0) ? default_alpha_ : a;
164
+
150
165
  SDL_SetRenderDrawColor(
151
166
  renderer_,
152
167
  static_cast<Uint8>(clamp(r)),
153
168
  static_cast<Uint8>(clamp(g)),
154
169
  static_cast<Uint8>(clamp(b)),
155
- 255
170
+ static_cast<Uint8>(clamp(a))
156
171
  );
157
172
  SDL_RenderFillRect(renderer_, &rect);
158
173
 
@@ -187,7 +202,7 @@ namespace mini {
187
202
  }
188
203
 
189
204
  // Draw text at specified position.
190
- void Engine::draw_text(const char* text, int x, int y, int r, int g, int b, int font_id)
205
+ void Engine::draw_text(const char* text, int x, int y, int r, int g, int b, int a, int font_id)
191
206
  {
192
207
  if (!initialized_ || renderer_ == nullptr) return;
193
208
 
@@ -202,7 +217,10 @@ namespace mini {
202
217
  return v;
203
218
  };
204
219
 
205
- SDL_Color color = { (Uint8)clamp(r), (Uint8)clamp(g), (Uint8)clamp(b), 255 };
220
+
221
+ // alpha or default alpha
222
+ a = (a < 0) ? default_alpha_ : a;
223
+ SDL_Color color = { (Uint8)clamp(r), (Uint8)clamp(g), (Uint8)clamp(b), static_cast<Uint8>(clamp(a)) };
206
224
 
207
225
  SDL_Surface* surface = TTF_RenderUTF8_Blended(font, text, color);
208
226
  if (!surface) {
@@ -217,6 +235,9 @@ namespace mini {
217
235
  return;
218
236
  }
219
237
 
238
+ SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
239
+ SDL_SetTextureAlphaMod(texture, static_cast<Uint8>(clamp(a)));
240
+
220
241
  SDL_Rect dstRect{ x, y, surface->w, surface->h };
221
242
  SDL_FreeSurface(surface);
222
243
 
@@ -224,30 +245,6 @@ namespace mini {
224
245
  SDL_DestroyTexture(texture);
225
246
  }
226
247
 
227
- void Engine::draw_rect_rgba(int x, int y, int w, int h, int r, int g, int b, int a)
228
- {
229
- if (!initialized_ || renderer_ == nullptr) {
230
- return;
231
- }
232
-
233
- auto clamp = [](int v) {
234
- if (v < 0) return 0;
235
- if (v > 255) return 255;
236
- return v;
237
- };
238
-
239
- SDL_Rect rect{ x, y, w, h };
240
-
241
- SDL_SetRenderDrawColor(
242
- renderer_,
243
- static_cast<Uint8>(clamp(r)),
244
- static_cast<Uint8>(clamp(g)),
245
- static_cast<Uint8>(clamp(b)),
246
- static_cast<Uint8>(clamp(a))
247
- );
248
- SDL_RenderFillRect(renderer_, &rect);
249
- }
250
-
251
248
  bool Engine::capture_frame(const char* path)
252
249
  {
253
250
  if (!initialized_ || renderer_ == nullptr) {
@@ -386,4 +383,26 @@ namespace mini {
386
383
  return events;
387
384
  }
388
385
 
386
+ std::pair<int, int> Engine::measure_text(const char* text, int font_id)
387
+ {
388
+ if (!initialized_) return {0, 0};
389
+
390
+ int idx = (font_id >= 0) ? font_id : default_font_id_;
391
+ if (idx < 0 || idx >= (int)fonts_.size() || fonts_[idx] == nullptr) return {0, 0};
392
+
393
+ if (text == nullptr || text[0] == '\0') return {0, 0};
394
+
395
+ int w = 0;
396
+ int h = 0;
397
+
398
+ // TTF_SizeUTF8 returns 0 on success, -1 on error
399
+ if (TTF_SizeUTF8(fonts_[idx], text, &w, &h) != 0) {
400
+ // Optional: log error
401
+ // std::cerr << "TTF_SizeUTF8 Error: " << TTF_GetError() << std::endl;
402
+ return {0, 0};
403
+ }
404
+
405
+ return {w, h};
406
+ }
407
+
389
408
  } // namespace mini
@@ -4,6 +4,7 @@
4
4
  #include <SDL_ttf.h>
5
5
  #include <vector>
6
6
  #include <string>
7
+ #include <utility>
7
8
 
8
9
  // A minimal 2D graphics engine binding for Python using SDL.
9
10
  namespace mini {
@@ -71,7 +72,7 @@ namespace mini {
71
72
  void end_frame();
72
73
 
73
74
  // Draw a simple filled rectangle (we'll use a fixed color for now).
74
- void draw_rect(int x, int y, int w, int h, int r, int g, int b);
75
+ void draw_rect(int x, int y, int w, int h, int r, int g, int b, int a);
75
76
 
76
77
  // Sprite drawing stub for later.
77
78
  void draw_sprite(int texture_id, int x, int y, int w, int h);
@@ -83,14 +84,15 @@ namespace mini {
83
84
  int load_font(const char* path, int pt_size);
84
85
 
85
86
  // Draw text at specified position.
86
- void draw_text(const char* text, int x, int y, int r, int g, int b, int font_id = -1);
87
+ void draw_text(const char* text, int x, int y, int r, int g, int b, int a, int font_id = -1);
87
88
 
88
89
  // Capture the current frame into an image file (BMP for now).
89
90
  // Returns true on success, false on failure.
90
91
  bool capture_frame(const char* path);
91
92
 
92
- // Draw a filled rectangle with RGBA color (supports transparency).
93
- void draw_rect_rgba(int x, int y, int w, int h, int r, int g, int b, int a);
93
+ // Measure text (UTF-8) using a loaded font.
94
+ // Returns (width, height) in pixels. Returns (0,0) if no valid font or error.
95
+ std::pair<int, int> measure_text(const char* text, int font_id = -1);
94
96
 
95
97
  private:
96
98
  SDL_Window* window_; // The main application window.
@@ -100,6 +102,7 @@ namespace mini {
100
102
  SDL_Color clear_color_; // The clear color for the screen.
101
103
  std::vector<TTF_Font*> fonts_; // Loaded fonts.
102
104
  int default_font_id_; // Default font index.
105
+ int default_alpha_; // Default alpha value for drawing.
103
106
  };
104
107
 
105
108
  } // namespace mini
@@ -8,7 +8,7 @@ build-backend = "scikit_build_core.build"
8
8
 
9
9
  [project]
10
10
  name = "mini-arcade-native-backend"
11
- version = "0.4.2"
11
+ version = "0.4.4"
12
12
  description = "Native SDL2 backend for mini-arcade-core using SDL2 + pybind11."
13
13
  authors = [
14
14
  { name = "Santiago Rincon", email = "rincores@gmail.com" },
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
  import os
8
8
  import sys
9
9
  from pathlib import Path
10
+ from typing import Union
10
11
 
11
12
  # --- 1) Make sure Windows can find SDL2.dll when using vcpkg ------------------
12
13
 
@@ -48,6 +49,7 @@ from . import _native as native
48
49
 
49
50
  __all__ = ["NativeBackend", "native"]
50
51
 
52
+ Alpha = Union[float, int]
51
53
 
52
54
  _NATIVE_TO_CORE = {
53
55
  native.EventType.Unknown: EventType.UNKNOWN,
@@ -208,6 +210,43 @@ class NativeBackend(Backend):
208
210
  """End the current frame for rendering."""
209
211
  self._engine.end_frame()
210
212
 
213
+ @staticmethod
214
+ def _alpha_to_u8(alpha: Alpha | None) -> int:
215
+ """Convert CSS-like alpha (0..1) to uint8 (0..255)."""
216
+ if alpha is None:
217
+ return 255
218
+
219
+ # disallow booleans (since bool is a subclass of int)
220
+ if isinstance(alpha, bool):
221
+ raise TypeError("alpha must be a float in [0,1], not bool")
222
+
223
+ a = float(alpha)
224
+
225
+ # Enforce “percentage only”
226
+ if a < 0.0 or a > 1.0:
227
+ raise ValueError(f"alpha must be in [0, 1], got {alpha!r}")
228
+
229
+ return int(round(a * 255))
230
+
231
+ @staticmethod
232
+ def _get_color_values(color: tuple[int, ...]) -> int:
233
+ """
234
+ Extract alpha value from color tuple (r,g,b) or (r,g,b,a).
235
+ If missing, returns default.
236
+ """
237
+ if len(color) == 3:
238
+ r, g, b = color
239
+ a_u8 = 255
240
+ elif len(color) == 4:
241
+ r, g, b, a = color
242
+ a_u8 = NativeBackend._alpha_to_u8(a)
243
+ else:
244
+ raise ValueError(
245
+ f"Color must be (r,g,b) or (r,g,b,a), got {color!r}"
246
+ )
247
+
248
+ return (int(r), int(g), int(b), a_u8)
249
+
211
250
  # pylint: disable=too-many-arguments,too-many-positional-arguments
212
251
  def draw_rect(
213
252
  self,
@@ -235,16 +274,8 @@ class NativeBackend(Backend):
235
274
  :param color: Color of the rectangle as (r, g, b) or (r, g, b, a).
236
275
  :type color: tuple[int, ...]
237
276
  """
238
- if len(color) == 3:
239
- r, g, b = color
240
- self._engine.draw_rect(x, y, w, h, r, g, b)
241
- elif len(color) == 4:
242
- r, g, b, a = color
243
- self._engine.draw_rect_rgba(x, y, w, h, r, g, b, a)
244
- else:
245
- raise ValueError(
246
- f"Color must be (r,g,b) or (r,g,b,a), got {color!r}"
247
- )
277
+ r, g, b, a = self._get_color_values(color)
278
+ self._engine.draw_rect(x, y, w, h, r, g, b, a)
248
279
 
249
280
  # pylint: enable=too-many-arguments,too-many-positional-arguments
250
281
 
@@ -271,12 +302,13 @@ class NativeBackend(Backend):
271
302
  :param color: Color of the text as (r, g, b).
272
303
  :type color: tuple[int, int, int]
273
304
  """
274
- # We rely on C++ side to no-op if font is missing
275
- r, g, b = color
305
+ r, g, b, a = self._get_color_values(color)
276
306
  font_id = (
277
307
  self._default_font_id if self._default_font_id is not None else -1
278
308
  )
279
- self._engine.draw_text(text, x, y, int(r), int(g), int(b), font_id)
309
+ self._engine.draw_text(
310
+ text, x, y, int(r), int(g), int(b), int(a), font_id
311
+ )
280
312
 
281
313
  def capture_frame(self, path: str | None = None) -> bool:
282
314
  """
@@ -289,4 +321,18 @@ class NativeBackend(Backend):
289
321
  False otherwise.
290
322
  :rtype: bool
291
323
  """
324
+ if path is None:
325
+ raise ValueError("Path must be provided to capture frame.")
292
326
  return self._engine.capture_frame(path)
327
+
328
+ def measure_text(self, text: str) -> tuple[int, int]:
329
+ """
330
+ Measure text size (width, height) in pixels for the active font.
331
+
332
+ Returns (0,0) if no font is loaded (matches draw_text no-op behavior).
333
+ """
334
+ font_id = (
335
+ self._default_font_id if self._default_font_id is not None else -1
336
+ )
337
+ w, h = self._engine.measure_text(text, font_id)
338
+ return int(w), int(h)
@@ -94,8 +94,8 @@ def setup_fake_core_and_native(monkeypatch):
94
94
  def end_frame(self):
95
95
  self.frames.append("end")
96
96
 
97
- def draw_rect(self, x, y, w, h, r, g, b):
98
- self.rects.append((x, y, w, h, r, g, b))
97
+ def draw_rect(self, x, y, w, h, r, g, b, a):
98
+ self.rects.append((x, y, w, h, r, g, b, a))
99
99
 
100
100
  fake_native.EventType = FakeNativeEventType
101
101
  fake_native.Engine = FakeEngine
@@ -248,7 +248,7 @@ def test_nativebackend_draw_rect_delegates_to_engine(backend_module):
248
248
  backend = pkg.NativeBackend()
249
249
  backend.draw_rect(10, 20, 30, 40, color=(255, 0, 0))
250
250
 
251
- assert backend._engine.rects == [(10, 20, 30, 40, 255, 0, 0)]
251
+ assert backend._engine.rects == [(10, 20, 30, 40, 255, 0, 0, 255)]
252
252
 
253
253
 
254
254
  def test_poll_events_maps_native_events_to_core_events_and_keys(