mini-arcade-native-backend 0.3.3__tar.gz → 0.4.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 (22) hide show
  1. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/.github/workflows/release-publish.yml +2 -1
  2. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/CHANGELOG.md +24 -0
  3. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/PKG-INFO +2 -2
  4. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/cpp/bindings.cpp +22 -2
  5. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/cpp/engine.cpp +80 -26
  6. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/cpp/engine.h +43 -10
  7. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/pyproject.toml +2 -2
  8. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/src/mini_arcade_native_backend/__init__.py +78 -8
  9. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/tests/test_init.py +83 -14
  10. mini_arcade_native_backend-0.3.3/.changelog_section +0 -6
  11. mini_arcade_native_backend-0.3.3/.version_to_tag +0 -1
  12. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/.github/workflows/ci.yml +0 -0
  13. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/.github/workflows/create-release-branch.yml +0 -0
  14. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/.github/workflows/release-finalize.yml +0 -0
  15. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/.gitignore +0 -0
  16. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/.vscode/settings.json +0 -0
  17. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/CMakeLists.txt +0 -0
  18. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/LICENSE +0 -0
  19. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/README.md +0 -0
  20. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/examples/native_backend_demo.py +0 -0
  21. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/poetry.lock +0 -0
  22. {mini_arcade_native_backend-0.3.3 → mini_arcade_native_backend-0.4.1}/poetry.toml +0 -0
@@ -17,8 +17,9 @@ jobs:
17
17
  with:
18
18
  release_branch: ${{ inputs.release_branch }}
19
19
  version_file: "pyproject.toml"
20
- apt-deps: "libsdl2-dev"
20
+ apt-deps: "libsdl2-dev libsdl2-ttf-dev"
21
21
  poetry-build-args: "-f sdist"
22
+ build_wheels: true
22
23
  secrets:
23
24
  PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
24
25
  SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
@@ -6,6 +6,30 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.1] - 2025-12-15
10
+
11
+ ### Added
12
+ - extend event handling with additional mouse and window events in engine and bindings
13
+ - enhance font handling by returning font ID and updating draw_text signature
14
+
15
+ ### Fixed
16
+ - update mini-arcade-core dependency version to ensure compatibility
17
+
18
+ ### Other
19
+ - Merge release/0.3 into develop
20
+
21
+ ## [0.4.0] - 2025-12-15
22
+
23
+ ### Added
24
+ - extend event handling with additional mouse and window events in engine and bindings
25
+ - enhance font handling by returning font ID and updating draw_text signature
26
+
27
+ ### Fixed
28
+ - update mini-arcade-core dependency version to ensure compatibility
29
+
30
+ ### Other
31
+ - Merge release/0.3 into develop
32
+
9
33
  ## [0.3.3] - 2025-12-05
10
34
 
11
35
  ### Other
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mini-arcade-native-backend
3
- Version: 0.3.3
3
+ Version: 0.4.1
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.2.3
27
+ Requires-Dist: mini-arcade-core~=0.9
28
28
  Provides-Extra: dev
29
29
  Requires-Dist: pytest~=8.3; extra == "dev"
30
30
  Requires-Dist: pytest-cov~=6.0; extra == "dev"
@@ -14,12 +14,31 @@ PYBIND11_MODULE(_native, m) {
14
14
  .value("Quit", mini::EventType::Quit)
15
15
  .value("KeyDown", mini::EventType::KeyDown)
16
16
  .value("KeyUp", mini::EventType::KeyUp)
17
+ .value("MouseMotion", mini::EventType::MouseMotion)
18
+ .value("MouseButtonDown", mini::EventType::MouseButtonDown)
19
+ .value("MouseButtonUp", mini::EventType::MouseButtonUp)
20
+ .value("MouseWheel", mini::EventType::MouseWheel)
21
+ .value("WindowResized", mini::EventType::WindowResized)
22
+ .value("TextInput", mini::EventType::TextInput)
17
23
  .export_values();
18
24
 
19
25
  // Bind the Event struct
20
26
  py::class_<mini::Event>(m, "Event")
21
27
  .def_readonly("type", &mini::Event::type)
22
- .def_readonly("key", &mini::Event::key);
28
+ .def_readonly("key", &mini::Event::key)
29
+ .def_readonly("scancode", &mini::Event::scancode)
30
+ .def_readonly("mod", &mini::Event::mod)
31
+ .def_readonly("repeat", &mini::Event::repeat)
32
+ .def_readonly("x", &mini::Event::x)
33
+ .def_readonly("y", &mini::Event::y)
34
+ .def_readonly("dx", &mini::Event::dx)
35
+ .def_readonly("dy", &mini::Event::dy)
36
+ .def_readonly("button", &mini::Event::button)
37
+ .def_readonly("wheel_x", &mini::Event::wheel_x)
38
+ .def_readonly("wheel_y", &mini::Event::wheel_y)
39
+ .def_readonly("width", &mini::Event::width)
40
+ .def_readonly("height", &mini::Event::height)
41
+ .def_readonly("text", &mini::Event::text);
23
42
 
24
43
  // Bind the Engine class
25
44
  py::class_<mini::Engine>(m, "Engine")
@@ -58,7 +77,8 @@ PYBIND11_MODULE(_native, m) {
58
77
  py::arg("y"),
59
78
  py::arg("r"),
60
79
  py::arg("g"),
61
- py::arg("b")
80
+ py::arg("b"),
81
+ py::arg("font_id") = -1
62
82
  )
63
83
  .def("poll_events", &mini::Engine::poll_events)
64
84
 
@@ -10,7 +10,8 @@ namespace mini {
10
10
  renderer_(nullptr),
11
11
  initialized_(false),
12
12
  font_(nullptr),
13
- clear_color_{0, 0, 0, 255}
13
+ clear_color_{0, 0, 0, 255},
14
+ default_font_id_(-1)
14
15
  {
15
16
  }
16
17
 
@@ -87,6 +88,8 @@ namespace mini {
87
88
  // Enable alpha blending for RGBA drawing
88
89
  SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
89
90
 
91
+ SDL_StartTextInput(); // <--- needed for SDL_TEXTINPUT
92
+
90
93
  initialized_ = true;
91
94
  }
92
95
 
@@ -161,40 +164,47 @@ namespace mini {
161
164
  }
162
165
 
163
166
  // Load a TTF font from file at specified point size.
164
- void Engine::load_font(const char* path, int pt_size)
167
+ int Engine::load_font(const char* path, int pt_size)
165
168
  {
166
169
  if (!initialized_) {
167
170
  throw std::runtime_error("Engine::init must be called before load_font");
168
171
  }
169
172
 
170
- if (font_ != nullptr) {
171
- TTF_CloseFont(font_);
172
- font_ = nullptr;
173
+ TTF_Font* f = TTF_OpenFont(path, pt_size);
174
+ if (!f) {
175
+ throw std::runtime_error(std::string("TTF_OpenFont Error: ") + TTF_GetError());
173
176
  }
174
177
 
175
- font_ = TTF_OpenFont(path, pt_size);
176
- if (!font_) {
177
- std::string msg = std::string("TTF_OpenFont Error: ") + TTF_GetError();
178
- throw std::runtime_error(msg);
178
+ fonts_.push_back(f);
179
+ int id = static_cast<int>(fonts_.size() - 1);
180
+
181
+ // first loaded font becomes default (good default behavior)
182
+ if (default_font_id_ < 0) {
183
+ default_font_id_ = id;
179
184
  }
185
+
186
+ return id;
180
187
  }
181
188
 
182
189
  // Draw text at specified position.
183
- void Engine::draw_text(const char* text, int x, int y, int r, int g, int b)
190
+ void Engine::draw_text(const char* text, int x, int y, int r, int g, int b, int font_id)
184
191
  {
185
- if (!initialized_ || renderer_ == nullptr || font_ == nullptr) {
186
- return;
187
- }
192
+ if (!initialized_ || renderer_ == nullptr) return;
193
+
194
+ int idx = (font_id >= 0) ? font_id : default_font_id_;
195
+ if (idx < 0 || idx >= (int)fonts_.size() || fonts_[idx] == nullptr) return;
196
+
197
+ TTF_Font* font = fonts_[idx];
188
198
 
189
- // clamp a bit to be safe
190
199
  auto clamp = [](int v) {
191
200
  if (v < 0) return 0;
192
201
  if (v > 255) return 255;
193
202
  return v;
194
203
  };
195
204
 
196
- SDL_Color color = { (Uint8)clamp(r), (Uint8)clamp(g), (Uint8)clamp(b), 255 }; // white text
197
- SDL_Surface* surface = TTF_RenderUTF8_Blended(font_, text, color);
205
+ SDL_Color color = { (Uint8)clamp(r), (Uint8)clamp(g), (Uint8)clamp(b), 255 };
206
+
207
+ SDL_Surface* surface = TTF_RenderUTF8_Blended(font, text, color);
198
208
  if (!surface) {
199
209
  std::cerr << "TTF_RenderUTF8_Blended Error: " << TTF_GetError() << std::endl;
200
210
  return;
@@ -207,12 +217,7 @@ namespace mini {
207
217
  return;
208
218
  }
209
219
 
210
- SDL_Rect dstRect;
211
- dstRect.x = x;
212
- dstRect.y = y;
213
- dstRect.w = surface->w;
214
- dstRect.h = surface->h;
215
-
220
+ SDL_Rect dstRect{ x, y, surface->w, surface->h };
216
221
  SDL_FreeSurface(surface);
217
222
 
218
223
  SDL_RenderCopy(renderer_, texture, nullptr, &dstRect);
@@ -243,7 +248,6 @@ namespace mini {
243
248
  SDL_RenderFillRect(renderer_, &rect);
244
249
  }
245
250
 
246
-
247
251
  bool Engine::capture_frame(const char* path)
248
252
  {
249
253
  if (!initialized_ || renderer_ == nullptr) {
@@ -303,8 +307,6 @@ namespace mini {
303
307
 
304
308
  while (SDL_PollEvent(&sdl_event)) {
305
309
  Event ev;
306
- ev.type = EventType::Unknown;
307
- ev.key = 0;
308
310
 
309
311
  switch (sdl_event.type) {
310
312
  case SDL_QUIT:
@@ -314,11 +316,63 @@ namespace mini {
314
316
  case SDL_KEYDOWN:
315
317
  ev.type = EventType::KeyDown;
316
318
  ev.key = sdl_event.key.keysym.sym;
319
+ ev.scancode = (int)sdl_event.key.keysym.scancode;
320
+ ev.mod = (int)sdl_event.key.keysym.mod;
321
+ ev.repeat = (int)sdl_event.key.repeat;
317
322
  break;
318
323
 
319
324
  case SDL_KEYUP:
320
325
  ev.type = EventType::KeyUp;
321
326
  ev.key = sdl_event.key.keysym.sym;
327
+ ev.scancode = (int)sdl_event.key.keysym.scancode;
328
+ ev.mod = (int)sdl_event.key.keysym.mod;
329
+ ev.repeat = 0;
330
+ break;
331
+
332
+ case SDL_MOUSEMOTION:
333
+ ev.type = EventType::MouseMotion;
334
+ ev.x = sdl_event.motion.x;
335
+ ev.y = sdl_event.motion.y;
336
+ ev.dx = sdl_event.motion.xrel;
337
+ ev.dy = sdl_event.motion.yrel;
338
+ break;
339
+
340
+ case SDL_MOUSEBUTTONDOWN:
341
+ ev.type = EventType::MouseButtonDown;
342
+ ev.button = (int)sdl_event.button.button;
343
+ ev.x = sdl_event.button.x;
344
+ ev.y = sdl_event.button.y;
345
+ break;
346
+
347
+ case SDL_MOUSEBUTTONUP:
348
+ ev.type = EventType::MouseButtonUp;
349
+ ev.button = (int)sdl_event.button.button;
350
+ ev.x = sdl_event.button.x;
351
+ ev.y = sdl_event.button.y;
352
+ break;
353
+
354
+ case SDL_MOUSEWHEEL:
355
+ ev.type = EventType::MouseWheel;
356
+ ev.wheel_x = sdl_event.wheel.x;
357
+ ev.wheel_y = sdl_event.wheel.y;
358
+ // If you want "natural" direction handling, you can flip based on sdl_event.wheel.direction
359
+ break;
360
+
361
+ case SDL_TEXTINPUT:
362
+ ev.type = EventType::TextInput;
363
+ ev.text = sdl_event.text.text; // UTF-8
364
+ break;
365
+
366
+ case SDL_WINDOWEVENT:
367
+ if (sdl_event.window.event == SDL_WINDOWEVENT_RESIZED ||
368
+ sdl_event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
369
+ {
370
+ ev.type = EventType::WindowResized;
371
+ ev.width = sdl_event.window.data1;
372
+ ev.height = sdl_event.window.data2;
373
+ } else {
374
+ continue; // ignore other window events
375
+ }
322
376
  break;
323
377
 
324
378
  default:
@@ -326,7 +380,7 @@ namespace mini {
326
380
  break;
327
381
  }
328
382
 
329
- events.push_back(ev);
383
+ events.push_back(std::move(ev));
330
384
  }
331
385
 
332
386
  return events;
@@ -3,6 +3,7 @@
3
3
  #include <SDL.h>
4
4
  #include <SDL_ttf.h>
5
5
  #include <vector>
6
+ #include <string>
6
7
 
7
8
  // A minimal 2D graphics engine binding for Python using SDL.
8
9
  namespace mini {
@@ -11,14 +12,44 @@ namespace mini {
11
12
  enum class EventType {
12
13
  Unknown = 0,
13
14
  Quit,
15
+
14
16
  KeyDown,
15
- KeyUp
17
+ KeyUp,
18
+
19
+ MouseMotion,
20
+ MouseButtonDown,
21
+ MouseButtonUp,
22
+ MouseWheel,
23
+
24
+ WindowResized,
25
+ TextInput
16
26
  };
17
27
 
18
28
  // A simple event structure to pass events to Python.
19
29
  struct Event {
20
- EventType type;
21
- int key; // SDL key code (e.g., 27 for ESC). 0 if not applicable.
30
+ EventType type = EventType::Unknown;
31
+
32
+ // Keyboard
33
+ int key = 0; // SDL_Keycode
34
+ int scancode = 0; // SDL_Scancode
35
+ int mod = 0; // SDL_Keymod bitmask
36
+ int repeat = 0; // 1 if key repeat, else 0
37
+
38
+ // Mouse
39
+ int x = 0;
40
+ int y = 0;
41
+ int dx = 0; // motion relative
42
+ int dy = 0;
43
+ int button = 0; // SDL_BUTTON_LEFT etc.
44
+ int wheel_x = 0;
45
+ int wheel_y = 0;
46
+
47
+ // Window
48
+ int width = 0;
49
+ int height = 0;
50
+
51
+ // Text input
52
+ std::string text;
22
53
  };
23
54
 
24
55
  // The main engine class that wraps SDL functionality.
@@ -49,10 +80,10 @@ namespace mini {
49
80
  std::vector<Event> poll_events();
50
81
 
51
82
  // Load a TTF font from file at specified point size.
52
- void load_font(const char* path, int pt_size);
83
+ int load_font(const char* path, int pt_size);
53
84
 
54
85
  // Draw text at specified position.
55
- void draw_text(const char* text, int x, int y, int r, int g, int b);
86
+ void draw_text(const char* text, int x, int y, int r, int g, int b, int font_id = -1);
56
87
 
57
88
  // Capture the current frame into an image file (BMP for now).
58
89
  // Returns true on success, false on failure.
@@ -62,11 +93,13 @@ namespace mini {
62
93
  void draw_rect_rgba(int x, int y, int w, int h, int r, int g, int b, int a);
63
94
 
64
95
  private:
65
- SDL_Window* window_;
66
- SDL_Renderer* renderer_;
67
- bool initialized_;
68
- TTF_Font* font_;
69
- SDL_Color clear_color_;
96
+ SDL_Window* window_; // The main application window.
97
+ SDL_Renderer* renderer_; // The renderer for drawing.
98
+ bool initialized_; // Whether the engine has been initialized.
99
+ TTF_Font* font_; // The current font for text rendering.
100
+ SDL_Color clear_color_; // The clear color for the screen.
101
+ std::vector<TTF_Font*> fonts_; // Loaded fonts.
102
+ int default_font_id_; // Default font index.
70
103
  };
71
104
 
72
105
  } // 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.3.3"
11
+ version = "0.4.1"
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" },
@@ -17,7 +17,7 @@ readme = "README.md"
17
17
  requires-python = ">=3.9,<3.12"
18
18
  license = { file = "LICENSE" }
19
19
  dependencies = [
20
- "mini-arcade-core>=0.2.3",
20
+ "mini-arcade-core~=0.9",
21
21
  ]
22
22
 
23
23
  [project.optional-dependencies]
@@ -6,17 +6,31 @@ from __future__ import annotations
6
6
 
7
7
  import os
8
8
  import sys
9
+ from pathlib import Path
9
10
 
10
11
  # --- 1) Make sure Windows can find SDL2.dll when using vcpkg ------------------
11
12
 
12
13
  if sys.platform == "win32":
14
+ # a) If running as a frozen PyInstaller exe (e.g. DejaBounce.exe),
15
+ # SDL2.dll will live next to the executable. Add that dir.
16
+ if getattr(sys, "frozen", False):
17
+ exe_dir = Path(sys.executable).resolve().parent
18
+ try:
19
+ os.add_dll_directory(str(exe_dir))
20
+ except (FileNotFoundError, OSError):
21
+ # If this somehow fails, we still try other fallbacks.
22
+ pass
23
+
24
+ # b) Dev / vcpkg fallback: use VCPKG_ROOT if available.
13
25
  vcpkg_root = os.environ.get("VCPKG_ROOT")
14
26
  if vcpkg_root:
15
27
  # Typical vcpkg layout: <VCPKG_ROOT>/installed/x64-windows/bin/SDL2.dll
16
28
  sdl_bin = os.path.join(vcpkg_root, "installed", "x64-windows", "bin")
17
29
  if os.path.isdir(sdl_bin):
18
- # Python 3.8+ – add DLL search path before importing the extension
19
- os.add_dll_directory(sdl_bin)
30
+ try:
31
+ os.add_dll_directory(sdl_bin)
32
+ except (FileNotFoundError, OSError):
33
+ pass
20
34
 
21
35
  # --- 2) Now import native extension and core types ----------------------------
22
36
 
@@ -39,6 +53,12 @@ _NATIVE_TO_CORE = {
39
53
  native.EventType.Quit: EventType.QUIT,
40
54
  native.EventType.KeyDown: EventType.KEYDOWN,
41
55
  native.EventType.KeyUp: EventType.KEYUP,
56
+ native.EventType.MouseMotion: EventType.MOUSEMOTION,
57
+ native.EventType.MouseButtonDown: EventType.MOUSEBUTTONDOWN,
58
+ native.EventType.MouseButtonUp: EventType.MOUSEBUTTONUP,
59
+ native.EventType.MouseWheel: EventType.MOUSEWHEEL,
60
+ native.EventType.WindowResized: EventType.WINDOWRESIZED,
61
+ native.EventType.TextInput: EventType.TEXTINPUT,
42
62
  }
43
63
 
44
64
 
@@ -56,6 +76,7 @@ class NativeBackend(Backend):
56
76
  self._engine = native.Engine()
57
77
  self._font_path = font_path
58
78
  self._font_size = font_size
79
+ self._default_font_id: int | None = None
59
80
 
60
81
  def init(self, width: int, height: int, title: str):
61
82
  """
@@ -74,7 +95,9 @@ class NativeBackend(Backend):
74
95
 
75
96
  # Load font if provided
76
97
  if self._font_path is not None:
77
- self._engine.load_font(self._font_path, self._font_size)
98
+ self._default_font_id = self._engine.load_font(
99
+ self._font_path, self._font_size
100
+ )
78
101
 
79
102
  def set_clear_color(self, r: int, g: int, b: int):
80
103
  """
@@ -91,6 +114,8 @@ class NativeBackend(Backend):
91
114
  """
92
115
  self._engine.set_clear_color(int(r), int(g), int(b))
93
116
 
117
+ # Justification: Many local variables needed for event mapping
118
+ # pylint: disable=too-many-locals
94
119
  def poll_events(self) -> list[Event]:
95
120
  """
96
121
  Poll for events from the backend and return them as a list of Event objects.
@@ -98,12 +123,54 @@ class NativeBackend(Backend):
98
123
  :return: List of Event objects representing the polled events.
99
124
  :rtype: list[Event]
100
125
  """
101
- events: list[Event] = []
126
+ out: list[Event] = []
102
127
  for ev in self._engine.poll_events():
103
- core_type = _NATIVE_TO_CORE.get(ev.type, EventType.UNKNOWN)
128
+ etype = _NATIVE_TO_CORE.get(ev.type, EventType.UNKNOWN)
129
+
130
+ # "0 means not present" convention from C++ side
104
131
  key = ev.key if getattr(ev, "key", 0) != 0 else None
105
- events.append(Event(type=core_type, key=key))
106
- return events
132
+
133
+ x = getattr(ev, "x", 0) or None
134
+ y = getattr(ev, "y", 0) or None
135
+ dx = getattr(ev, "dx", 0) or None
136
+ dy = getattr(ev, "dy", 0) or None
137
+ button = getattr(ev, "button", 0) or None
138
+
139
+ wheel_x = getattr(ev, "wheel_x", 0)
140
+ wheel_y = getattr(ev, "wheel_y", 0)
141
+ wheel = (wheel_x, wheel_y) if (wheel_x or wheel_y) else None
142
+
143
+ w = getattr(ev, "width", 0)
144
+ h = getattr(ev, "height", 0)
145
+ size = (w, h) if (w and h) else None
146
+
147
+ text = getattr(ev, "text", "") or None
148
+
149
+ scancode = getattr(ev, "scancode", 0) or None
150
+ mod = getattr(ev, "mod", 0) or None
151
+ repeat_raw = getattr(ev, "repeat", 0)
152
+ repeat = bool(repeat_raw) if repeat_raw else None
153
+
154
+ out.append(
155
+ Event(
156
+ type=etype,
157
+ key=key,
158
+ x=x,
159
+ y=y,
160
+ dx=dx,
161
+ dy=dy,
162
+ button=button,
163
+ wheel=wheel,
164
+ size=size,
165
+ text=text,
166
+ scancode=scancode,
167
+ mod=mod,
168
+ repeat=repeat,
169
+ )
170
+ )
171
+ return out
172
+
173
+ # pylint: enable=too-many-locals
107
174
 
108
175
  def begin_frame(self):
109
176
  """Begin a new frame for rendering."""
@@ -178,7 +245,10 @@ class NativeBackend(Backend):
178
245
  """
179
246
  # We rely on C++ side to no-op if font is missing
180
247
  r, g, b = color
181
- self._engine.draw_text(text, x, y, int(r), int(g), int(b))
248
+ font_id = (
249
+ self._default_font_id if self._default_font_id is not None else -1
250
+ )
251
+ self._engine.draw_text(text, x, y, int(r), int(g), int(b), font_id)
182
252
 
183
253
  def capture_frame(self, path: str | None = None) -> bool:
184
254
  """
@@ -18,18 +18,33 @@ def setup_fake_core_and_native(monkeypatch):
18
18
  class FakeBackend:
19
19
  """Minimal base class just to satisfy inheritance."""
20
20
 
21
- pass
22
-
23
21
  class FakeCoreEventType:
24
22
  UNKNOWN = "core_unknown"
25
23
  QUIT = "core_quit"
26
24
  KEYDOWN = "core_keydown"
27
25
  KEYUP = "core_keyup"
26
+ MOUSEMOTION = "core_mousemotion"
27
+ MOUSEBUTTONDOWN = "core_mousebuttondown"
28
+ MOUSEBUTTONUP = "core_mousebuttonup"
29
+ MOUSEWHEEL = "core_mousewheel"
30
+ WINDOWRESIZED = "core_windowresized"
31
+ TEXTINPUT = "core_textinput"
28
32
 
29
33
  @dataclass
30
34
  class FakeEvent:
31
35
  type: object
32
36
  key: object = None
37
+ x: object = None
38
+ y: object = None
39
+ dx: object = None
40
+ dy: object = None
41
+ button: object = None
42
+ wheel: object = None
43
+ size: object = None
44
+ text: object = None
45
+ scancode: object = None
46
+ mod: object = None
47
+ repeat: object = None
33
48
 
34
49
  fake_core.Backend = FakeBackend
35
50
  fake_core.Event = FakeEvent
@@ -45,6 +60,12 @@ def setup_fake_core_and_native(monkeypatch):
45
60
  Quit = "native_quit"
46
61
  KeyDown = "native_keydown"
47
62
  KeyUp = "native_keyup"
63
+ MouseMotion = "native_mousemotion"
64
+ MouseButtonDown = "native_mousebuttondown"
65
+ MouseButtonUp = "native_mousebuttonup"
66
+ MouseWheel = "native_mousewheel"
67
+ WindowResized = "native_windowresized"
68
+ TextInput = "native_textinput"
48
69
 
49
70
  class FakeEngine:
50
71
  def __init__(self):
@@ -226,36 +247,84 @@ def test_poll_events_maps_native_events_to_core_events_and_keys(
226
247
  backend_module,
227
248
  ):
228
249
  pkg, fake_core, fake_native = backend_module
229
-
230
250
  backend = pkg.NativeBackend()
231
251
 
232
- # Create fake native events
233
252
  @dataclass
234
253
  class FakeNativeEvent:
235
254
  type: object
236
255
  key: int = 0
256
+ x: int = 0
257
+ y: int = 0
258
+ dx: int = 0
259
+ dy: int = 0
260
+ button: int = 0
261
+ wheel_x: int = 0
262
+ wheel_y: int = 0
263
+ width: int = 0
264
+ height: int = 0
265
+ text: str = ""
266
+ scancode: int = 0
267
+ mod: int = 0
268
+ repeat: int = 0
237
269
 
238
270
  engine = backend._engine
239
271
  engine._events_to_return = [
240
- FakeNativeEvent(fake_native.EventType.Quit, 0), # -> QUIT, key=None
272
+ FakeNativeEvent(fake_native.EventType.Quit), # -> QUIT
241
273
  FakeNativeEvent(
242
- fake_native.EventType.KeyDown, 32
274
+ fake_native.EventType.KeyDown, key=32
243
275
  ), # -> KEYDOWN, key=32
244
- FakeNativeEvent("something_unknown", 10), # -> UNKNOWN, key=10
276
+ FakeNativeEvent(
277
+ fake_native.EventType.MouseMotion, x=10, y=20, dx=1, dy=-2
278
+ ),
279
+ FakeNativeEvent(
280
+ fake_native.EventType.MouseButtonDown, x=5, y=6, button=1
281
+ ),
282
+ FakeNativeEvent(
283
+ fake_native.EventType.MouseWheel, wheel_x=0, wheel_y=-1
284
+ ),
285
+ FakeNativeEvent(
286
+ fake_native.EventType.WindowResized, width=800, height=600
287
+ ),
288
+ FakeNativeEvent(fake_native.EventType.TextInput, text="á"),
289
+ FakeNativeEvent("something_unknown", key=10), # -> UNKNOWN, key=10
245
290
  ]
246
291
 
247
292
  events = backend.poll_events()
293
+ assert len(events) == 8
248
294
 
249
- assert len(events) == 3
250
-
251
- # 1) Quit event, key==0 -> key should become None
295
+ # Quit: key 0 -> None
252
296
  assert events[0].type == fake_core.EventType.QUIT
253
297
  assert events[0].key is None
254
298
 
255
- # 2) KeyDown, key!=0 passes through
299
+ # KeyDown: key passes through
256
300
  assert events[1].type == fake_core.EventType.KEYDOWN
257
301
  assert events[1].key == 32
258
302
 
259
- # 3) Unknown type -> UNKNOWN
260
- assert events[2].type == fake_core.EventType.UNKNOWN
261
- assert events[2].key == 10
303
+ # MouseMotion: x/y/dx/dy mapped (0 becomes None; here non-zero)
304
+ assert events[2].type == fake_core.EventType.MOUSEMOTION
305
+ assert events[2].x == 10
306
+ assert events[2].y == 20
307
+ assert events[2].dx == 1
308
+ assert events[2].dy == -2
309
+
310
+ # MouseButtonDown
311
+ assert events[3].type == fake_core.EventType.MOUSEBUTTONDOWN
312
+ assert events[3].x == 5
313
+ assert events[3].y == 6
314
+ assert events[3].button == 1
315
+
316
+ # MouseWheel -> wheel tuple
317
+ assert events[4].type == fake_core.EventType.MOUSEWHEEL
318
+ assert events[4].wheel == (0, -1)
319
+
320
+ # WindowResized -> size tuple
321
+ assert events[5].type == fake_core.EventType.WINDOWRESIZED
322
+ assert events[5].size == (800, 600)
323
+
324
+ # TextInput -> text
325
+ assert events[6].type == fake_core.EventType.TEXTINPUT
326
+ assert events[6].text == "á"
327
+
328
+ # Unknown
329
+ assert events[7].type == fake_core.EventType.UNKNOWN
330
+ assert events[7].key == 10
@@ -1,6 +0,0 @@
1
- ## [0.3.3] - 2025-12-05
2
-
3
- ### Other
4
- - docs: improve docstring formatting for capture_frame method in NativeBackend
5
- - Merge branch 'release/0.3' of https://github.com/alexsc6955/mini-arcade-native-backend into release/0.3
6
-
@@ -1 +0,0 @@
1
- 0.3.3