mini-arcade-native-backend 0.4.6__tar.gz → 0.5.3__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.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/ci.yml +1 -1
  2. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/release-publish.yml +1 -1
  3. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/CHANGELOG.md +40 -0
  4. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/CMakeLists.txt +2 -1
  5. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/PKG-INFO +2 -2
  6. mini_arcade_native_backend-0.5.3/cpp/bindings.cpp +126 -0
  7. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/cpp/engine.cpp +177 -15
  8. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/cpp/engine.h +32 -1
  9. mini_arcade_native_backend-0.5.3/poetry.lock +1004 -0
  10. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/pyproject.toml +2 -2
  11. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/src/mini_arcade_native_backend/__init__.py +162 -23
  12. mini_arcade_native_backend-0.4.6/cpp/bindings.cpp +0 -89
  13. mini_arcade_native_backend-0.4.6/poetry.lock +0 -23
  14. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/create-release-branch.yml +0 -0
  15. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/release-finalize.yml +0 -0
  16. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.gitignore +0 -0
  17. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.vscode/settings.json +0 -0
  18. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/LICENSE +0 -0
  19. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/README.md +0 -0
  20. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/examples/native_backend_demo.py +0 -0
  21. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/poetry.toml +0 -0
  22. {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/tests/test_init.py +0 -0
@@ -19,7 +19,7 @@ jobs:
19
19
  with:
20
20
  project-name: "mini-arcade-native-backend"
21
21
  lint-path: "src/mini_arcade_native_backend"
22
- apt-deps: "libsdl2-dev libsdl2-ttf-dev"
22
+ apt-deps: "libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev"
23
23
  secrets:
24
24
  SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
25
25
  SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}
@@ -17,7 +17,7 @@ jobs:
17
17
  with:
18
18
  release_branch: ${{ inputs.release_branch }}
19
19
  version_file: "pyproject.toml"
20
- apt-deps: "libsdl2-dev libsdl2-ttf-dev"
20
+ apt-deps: "libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev"
21
21
  poetry-build-args: "-f sdist"
22
22
  build_wheels: true
23
23
  secrets:
@@ -6,6 +6,46 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.3] - 2026-01-23
10
+
11
+ - Internal changes only.
12
+
13
+ ## [0.5.2] - 2026-01-23
14
+
15
+ ### Added
16
+ - add window resizing and clipping functions to Engine and update bindings
17
+ - add audio management functions to Engine and integrate with NativeBackend
18
+
19
+ ### Fixed
20
+ - update apt dependencies to include libsdl2-mixer-dev for CI and release workflows
21
+
22
+ ### Changed
23
+ - add TODO for backend interface refactoring to improve structure
24
+
25
+ ## [0.5.1] - 2026-01-23
26
+
27
+ ### Added
28
+ - add window resizing and clipping functions to Engine and update bindings
29
+ - add audio management functions to Engine and integrate with NativeBackend
30
+
31
+ ### Fixed
32
+ - update apt dependencies to include libsdl2-mixer-dev for CI and release workflows
33
+
34
+ ### Changed
35
+ - add TODO for backend interface refactoring to improve structure
36
+
37
+ ## [0.5.0] - 2026-01-21
38
+
39
+ ### Added
40
+ - add set_window_title method to Engine and update init method to accept optional title
41
+
42
+ ### Fixed
43
+ - Update dependencies and improve import handling for mini-arcade-core
44
+ - update import statements for Event and SDL_KEYCODE_TO_KEY to improve clarity
45
+
46
+ ### Other
47
+ - Merge branch 'develop' into release/0.5
48
+
9
49
  ## [0.4.6] - 2025-12-26
10
50
 
11
51
  ### Fixed
@@ -10,6 +10,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
10
10
  find_package(pybind11 CONFIG REQUIRED)
11
11
  find_package(SDL2 CONFIG REQUIRED)
12
12
  find_package(SDL2_ttf CONFIG REQUIRED)
13
+ find_package(SDL2_mixer CONFIG REQUIRED)
13
14
 
14
15
  set(TARGET_NAME _native)
15
16
 
@@ -19,7 +20,7 @@ pybind11_add_module(${TARGET_NAME}
19
20
  cpp/engine.cpp
20
21
  )
21
22
 
22
- target_link_libraries(${TARGET_NAME} PRIVATE SDL2::SDL2 SDL2_ttf::SDL2_ttf)
23
+ target_link_libraries(${TARGET_NAME} PRIVATE SDL2::SDL2 SDL2_ttf::SDL2_ttf SDL2_mixer::SDL2_mixer)
23
24
 
24
25
  # Install the compiled extension into the Python package directory
25
26
  # so it ends up as mini_arcade_native_backend/_native.*.pyd
@@ -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,126 @@
1
+ #include <pybind11/pybind11.h>
2
+ #include <pybind11/stl.h> // so std::vector<Event> becomes a Python list
3
+
4
+ #include "engine.h"
5
+
6
+ namespace py = pybind11;
7
+
8
+ PYBIND11_MODULE(_native, m) {
9
+ m.doc() = "Mini arcade native SDL2 backend";
10
+
11
+ // Bind the EventType enum
12
+ py::enum_<mini::EventType>(m, "EventType")
13
+ .value("Unknown", mini::EventType::Unknown)
14
+ .value("Quit", mini::EventType::Quit)
15
+ .value("KeyDown", mini::EventType::KeyDown)
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)
23
+ .export_values();
24
+
25
+ // Bind the Event struct
26
+ py::class_<mini::Event>(m, "Event")
27
+ .def_readonly("type", &mini::Event::type)
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);
42
+
43
+ // Bind the Engine class
44
+ py::class_<mini::Engine>(m, "Engine")
45
+ .def(py::init<>())
46
+ .def("init", &mini::Engine::init,
47
+ py::arg("width"), py::arg("height"), py::arg("title"))
48
+
49
+
50
+ .def("set_window_title", &mini::Engine::set_window_title,
51
+ py::arg("title"))
52
+
53
+ .def("set_clear_color", &mini::Engine::set_clear_color,
54
+ py::arg("r"), py::arg("g"), py::arg("b"))
55
+
56
+ .def("begin_frame", &mini::Engine::begin_frame)
57
+ .def("end_frame", &mini::Engine::end_frame)
58
+
59
+ .def("draw_rect", &mini::Engine::draw_rect,
60
+ py::arg("x"), py::arg("y"),
61
+ py::arg("w"), py::arg("h"),
62
+ py::arg("r"), py::arg("g"), py::arg("b"), py::arg("a"))
63
+
64
+ .def("draw_sprite", &mini::Engine::draw_sprite,
65
+ py::arg("texture_id"), py::arg("x"), py::arg("y"),
66
+ py::arg("w"), py::arg("h"))
67
+
68
+ .def("load_font", &mini::Engine::load_font,
69
+ py::arg("path"), py::arg("pt_size"))
70
+
71
+ .def(
72
+ "draw_text",
73
+ &mini::Engine::draw_text,
74
+ py::arg("text"),
75
+ py::arg("x"),
76
+ py::arg("y"),
77
+ py::arg("r"),
78
+ py::arg("g"),
79
+ py::arg("b"),
80
+ py::arg("a"),
81
+ py::arg("font_id") = -1
82
+ )
83
+ .def("poll_events", &mini::Engine::poll_events)
84
+
85
+ .def("capture_frame", &mini::Engine::capture_frame,
86
+ py::arg("path"))
87
+ .def(
88
+ "measure_text",
89
+ &mini::Engine::measure_text,
90
+ py::arg("text"),
91
+ py::arg("font_id") = -1
92
+ )
93
+
94
+ .def("init_audio", &mini::Engine::init_audio,
95
+ py::arg("frequency") = 44100,
96
+ py::arg("channels") = 2,
97
+ py::arg("chunk_size") = 2048)
98
+
99
+ .def("shutdown_audio", &mini::Engine::shutdown_audio)
100
+
101
+ .def("load_sound", &mini::Engine::load_sound,
102
+ py::arg("sound_id"),
103
+ py::arg("path"))
104
+
105
+ .def("play_sound", &mini::Engine::play_sound,
106
+ py::arg("sound_id"),
107
+ py::arg("loops") = 0)
108
+
109
+ .def("set_master_volume", &mini::Engine::set_master_volume,
110
+ py::arg("volume"))
111
+
112
+ .def("set_sound_volume", &mini::Engine::set_sound_volume,
113
+ py::arg("sound_id"),
114
+ py::arg("volume"))
115
+
116
+ .def("stop_all_sounds", &mini::Engine::stop_all_sounds)
117
+ .def("resize_window", &mini::Engine::resize_window,
118
+ py::arg("width"),
119
+ py::arg("height"))
120
+ .def("set_clip_rect", &mini::Engine::set_clip_rect,
121
+ py::arg("x"),
122
+ py::arg("y"),
123
+ py::arg("w"),
124
+ py::arg("h"))
125
+ .def("clear_clip_rect", &mini::Engine::clear_clip_rect);
126
+ }
@@ -11,7 +11,6 @@ namespace mini {
11
11
  : window_(nullptr),
12
12
  renderer_(nullptr),
13
13
  initialized_(false),
14
- font_(nullptr),
15
14
  clear_color_{0, 0, 0, 255},
16
15
  default_font_id_(-1),
17
16
  default_alpha_(255)
@@ -20,10 +19,12 @@ namespace mini {
20
19
 
21
20
  Engine::~Engine()
22
21
  {
23
- if (font_ != nullptr) {
24
- TTF_CloseFont(font_);
25
- font_ = nullptr;
22
+ shutdown_audio();
23
+
24
+ for (TTF_Font* f : fonts_) {
25
+ if (f) TTF_CloseFont(f);
26
26
  }
27
+ fonts_.clear();
27
28
 
28
29
  if (renderer_ != nullptr) {
29
30
  SDL_DestroyRenderer(renderer_);
@@ -35,19 +36,14 @@ namespace mini {
35
36
  window_ = nullptr;
36
37
  }
37
38
 
39
+ SDL_StopTextInput();
40
+
38
41
  if (initialized_) {
42
+ TTF_Quit();
39
43
  SDL_Quit();
40
44
  initialized_ = false;
41
45
  }
42
46
 
43
- for (TTF_Font* f : fonts_) {
44
- if (f) TTF_CloseFont(f);
45
- }
46
- fonts_.clear();
47
-
48
- TTF_Quit();
49
- SDL_StopTextInput();
50
-
51
47
  }
52
48
 
53
49
  void Engine::init(int width, int height, const char* title)
@@ -72,7 +68,7 @@ namespace mini {
72
68
  SDL_WINDOWPOS_CENTERED,
73
69
  width,
74
70
  height,
75
- SDL_WINDOW_SHOWN
71
+ SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE
76
72
  );
77
73
 
78
74
  if (window_ == nullptr) {
@@ -87,6 +83,7 @@ namespace mini {
87
83
  -1,
88
84
  SDL_RENDERER_ACCELERATED
89
85
  );
86
+ // SDL_RenderSetLogicalSize(renderer_, width, height);
90
87
 
91
88
  if (renderer_ == nullptr) {
92
89
  std::string msg = std::string("SDL_CreateRenderer Error: ") + SDL_GetError();
@@ -105,6 +102,12 @@ namespace mini {
105
102
  initialized_ = true;
106
103
  }
107
104
 
105
+ void Engine::set_window_title(const char* title)
106
+ {
107
+ if (!initialized_ || !window_) return;
108
+ SDL_SetWindowTitle(window_, title ? title : "");
109
+ }
110
+
108
111
  void Engine::set_clear_color(int r, int g, int b)
109
112
  {
110
113
  auto clamp = [](int v) {
@@ -302,6 +305,21 @@ namespace mini {
302
305
  std::vector<Event> events;
303
306
  SDL_Event sdl_event;
304
307
 
308
+ auto scale_mouse = [&](int &x, int &y, int &dx, int &dy) {
309
+ int ww=0, wh=0, rw=0, rh=0;
310
+ SDL_GetWindowSize(window_, &ww, &wh);
311
+ SDL_GetRendererOutputSize(renderer_, &rw, &rh);
312
+
313
+ if (ww > 0 && wh > 0) {
314
+ float sx = (float)rw / (float)ww;
315
+ float sy = (float)rh / (float)wh;
316
+ x = (int)lroundf(x * sx);
317
+ y = (int)lroundf(y * sy);
318
+ dx = (int)lroundf(dx * sx);
319
+ dy = (int)lroundf(dy * sy);
320
+ }
321
+ };
322
+
305
323
  while (SDL_PollEvent(&sdl_event)) {
306
324
  Event ev;
307
325
 
@@ -332,6 +350,7 @@ namespace mini {
332
350
  ev.y = sdl_event.motion.y;
333
351
  ev.dx = sdl_event.motion.xrel;
334
352
  ev.dy = sdl_event.motion.yrel;
353
+ scale_mouse(ev.x, ev.y, ev.dx, ev.dy);
335
354
  break;
336
355
 
337
356
  case SDL_MOUSEBUTTONDOWN:
@@ -346,6 +365,7 @@ namespace mini {
346
365
  ev.button = (int)sdl_event.button.button;
347
366
  ev.x = sdl_event.button.x;
348
367
  ev.y = sdl_event.button.y;
368
+ scale_mouse(ev.x, ev.y, ev.dx, ev.dy);
349
369
  break;
350
370
 
351
371
  case SDL_MOUSEWHEEL:
@@ -365,8 +385,14 @@ namespace mini {
365
385
  sdl_event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
366
386
  {
367
387
  ev.type = EventType::WindowResized;
368
- ev.width = sdl_event.window.data1;
369
- ev.height = sdl_event.window.data2;
388
+ int rw=0, rh=0;
389
+ if (renderer_ && SDL_GetRendererOutputSize(renderer_, &rw, &rh) == 0) {
390
+ ev.width = rw;
391
+ ev.height = rh;
392
+ } else {
393
+ ev.width = sdl_event.window.data1;
394
+ ev.height = sdl_event.window.data2;
395
+ }
370
396
  } else {
371
397
  continue; // ignore other window events
372
398
  }
@@ -405,4 +431,140 @@ namespace mini {
405
431
  return {w, h};
406
432
  }
407
433
 
434
+ void Engine::init_audio(int frequency, int channels, int chunk_size)
435
+ {
436
+ if (audio_initialized_) return;
437
+
438
+ // Make sure SDL audio subsystem is enabled
439
+ if ((SDL_WasInit(SDL_INIT_AUDIO) & SDL_INIT_AUDIO) == 0) {
440
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
441
+ throw std::runtime_error(std::string("SDL_InitSubSystem(AUDIO) Error: ") + SDL_GetError());
442
+ }
443
+ }
444
+
445
+ if (Mix_OpenAudio(frequency, MIX_DEFAULT_FORMAT, channels, chunk_size) != 0) {
446
+ throw std::runtime_error(std::string("Mix_OpenAudio Error: ") + Mix_GetError());
447
+ }
448
+
449
+ // Optional: allow OGG/MP3 decoding if you want
450
+ // Mix_Init(MIX_INIT_OGG | MIX_INIT_MP3);
451
+
452
+ Mix_AllocateChannels(16); // plenty for pong
453
+ Mix_Volume(-1, master_volume_); // -1 = all channels
454
+
455
+ audio_initialized_ = true;
456
+ }
457
+
458
+ void Engine::shutdown_audio()
459
+ {
460
+ if (!audio_initialized_) return;
461
+
462
+ stop_all_sounds();
463
+
464
+ for (auto& kv : sounds_) {
465
+ if (kv.second) {
466
+ Mix_FreeChunk(kv.second);
467
+ }
468
+ }
469
+ sounds_.clear();
470
+
471
+ Mix_CloseAudio();
472
+ // Mix_Quit(); // if you used Mix_Init
473
+
474
+ audio_initialized_ = false;
475
+ }
476
+
477
+ void Engine::load_sound(const std::string& sound_id, const std::string& path)
478
+ {
479
+ if (!audio_initialized_) {
480
+ // auto-init so Python doesn't need to care
481
+ init_audio();
482
+ }
483
+
484
+ if (sound_id.empty()) {
485
+ throw std::runtime_error("load_sound: sound_id is empty");
486
+ }
487
+
488
+ // If already loaded, free it first (support hot reload)
489
+ auto it = sounds_.find(sound_id);
490
+ if (it != sounds_.end() && it->second) {
491
+ Mix_FreeChunk(it->second);
492
+ it->second = nullptr;
493
+ }
494
+
495
+ Mix_Chunk* chunk = Mix_LoadWAV(path.c_str());
496
+ if (!chunk) {
497
+ throw std::runtime_error(std::string("Mix_LoadWAV Error: ") + Mix_GetError());
498
+ }
499
+
500
+ sounds_[sound_id] = chunk;
501
+ }
502
+
503
+ void Engine::play_sound(const std::string& sound_id, int loops)
504
+ {
505
+ if (!audio_initialized_) {
506
+ init_audio();
507
+ }
508
+
509
+ auto it = sounds_.find(sound_id);
510
+ if (it == sounds_.end() || it->second == nullptr) {
511
+ // be forgiving: if not preloaded, just do nothing
512
+ // (or throw if you want strict behavior)
513
+ return;
514
+ }
515
+
516
+ Mix_Chunk* chunk = it->second;
517
+
518
+ // -1 channel = pick first free channel
519
+ Mix_PlayChannel(-1, chunk, loops);
520
+ }
521
+
522
+ void Engine::set_master_volume(int volume)
523
+ {
524
+ if (volume < 0) volume = 0;
525
+ if (volume > MIX_MAX_VOLUME) volume = MIX_MAX_VOLUME;
526
+
527
+ master_volume_ = volume;
528
+ Mix_Volume(-1, master_volume_); // all channels
529
+ }
530
+
531
+ void Engine::set_sound_volume(const std::string& sound_id, int volume)
532
+ {
533
+ if (volume < 0) volume = 0;
534
+ if (volume > MIX_MAX_VOLUME) volume = MIX_MAX_VOLUME;
535
+
536
+ auto it = sounds_.find(sound_id);
537
+ if (it == sounds_.end() || it->second == nullptr) return;
538
+
539
+ Mix_VolumeChunk(it->second, volume);
540
+ }
541
+
542
+ void Engine::stop_all_sounds()
543
+ {
544
+ Mix_HaltChannel(-1);
545
+ }
546
+
547
+ void Engine::resize_window(int width, int height)
548
+ {
549
+ if (!initialized_ || !window_) return;
550
+ SDL_SetWindowSize(window_, width, height);
551
+
552
+ // if (renderer_) {
553
+ // SDL_RenderSetLogicalSize(renderer_, width, height);
554
+ // }
555
+ }
556
+
557
+ void Engine::set_clip_rect(int x, int y, int w, int h)
558
+ {
559
+ if (!initialized_ || renderer_ == nullptr) return;
560
+ SDL_Rect r{ x, y, w, h };
561
+ SDL_RenderSetClipRect(renderer_, &r);
562
+ }
563
+
564
+ void Engine::clear_clip_rect()
565
+ {
566
+ if (!initialized_ || renderer_ == nullptr) return;
567
+ SDL_RenderSetClipRect(renderer_, nullptr);
568
+ }
569
+
408
570
  } // namespace mini
@@ -2,6 +2,8 @@
2
2
 
3
3
  #include <SDL.h>
4
4
  #include <SDL_ttf.h>
5
+ #include <SDL_mixer.h>
6
+ #include <unordered_map>
5
7
  #include <vector>
6
8
  #include <string>
7
9
  #include <utility>
@@ -62,6 +64,9 @@ namespace mini {
62
64
  // Initialize the engine with a window of given width, height, and title.
63
65
  void init(int width, int height, const char* title);
64
66
 
67
+ // Set the window title.
68
+ void set_window_title(const char* title);
69
+
65
70
  // Set the clear color for the screen.
66
71
  void set_clear_color(int r, int g, int b);
67
72
 
@@ -94,15 +99,41 @@ namespace mini {
94
99
  // Returns (width, height) in pixels. Returns (0,0) if no valid font or error.
95
100
  std::pair<int, int> measure_text(const char* text, int font_id = -1);
96
101
 
102
+ // --- Audio ---
103
+ // Initialize audio subsystem.
104
+ void init_audio(int frequency = 44100, int channels = 2, int chunk_size = 2048);
105
+ // Shutdown audio subsystem.
106
+ void shutdown_audio();
107
+
108
+ // Load, play, and manage sounds.
109
+ void load_sound(const std::string& sound_id, const std::string& path);
110
+ // Play a loaded sound by its ID.
111
+ void play_sound(const std::string& sound_id, int loops = 0);
112
+ // Set the volume for a specific sound (0..128).
113
+ void set_sound_volume(const std::string& sound_id, int volume); // 0..128
114
+ // Set the master volume for all sounds (0..128).
115
+ void set_master_volume(int volume); // 0..128
116
+ // Stop all currently playing sounds.
117
+ void stop_all_sounds();
118
+
119
+ // Resize the application window.
120
+ void resize_window(int width, int height);
121
+ // Set clipping rectangle for rendering.
122
+ void set_clip_rect(int x, int y, int w, int h);
123
+ // Clear clipping rectangle (disable clipping).
124
+ void clear_clip_rect();
125
+
97
126
  private:
98
127
  SDL_Window* window_; // The main application window.
99
128
  SDL_Renderer* renderer_; // The renderer for drawing.
100
129
  bool initialized_; // Whether the engine has been initialized.
101
- TTF_Font* font_; // The current font for text rendering.
102
130
  SDL_Color clear_color_; // The clear color for the screen.
103
131
  std::vector<TTF_Font*> fonts_; // Loaded fonts.
104
132
  int default_font_id_; // Default font index.
105
133
  int default_alpha_; // Default alpha value for drawing.
134
+ bool audio_initialized_ = false; // Whether audio subsystem is initialized.
135
+ int master_volume_ = MIX_MAX_VOLUME; // 128 is max volume
136
+ std::unordered_map<std::string, Mix_Chunk*> sounds_; // Audio data
106
137
  };
107
138
 
108
139
  } // namespace mini