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.
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/ci.yml +1 -1
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/release-publish.yml +1 -1
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/CHANGELOG.md +40 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/CMakeLists.txt +2 -1
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/PKG-INFO +2 -2
- mini_arcade_native_backend-0.5.3/cpp/bindings.cpp +126 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/cpp/engine.cpp +177 -15
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/cpp/engine.h +32 -1
- mini_arcade_native_backend-0.5.3/poetry.lock +1004 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/pyproject.toml +2 -2
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/src/mini_arcade_native_backend/__init__.py +162 -23
- mini_arcade_native_backend-0.4.6/cpp/bindings.cpp +0 -89
- mini_arcade_native_backend-0.4.6/poetry.lock +0 -23
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/create-release-branch.yml +0 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/release-finalize.yml +0 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.gitignore +0 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.vscode/settings.json +0 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/LICENSE +0 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/README.md +0 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/examples/native_backend_demo.py +0 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/poetry.toml +0 -0
- {mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/tests/test_init.py +0 -0
{mini_arcade_native_backend-0.4.6 → mini_arcade_native_backend-0.5.3}/.github/workflows/ci.yml
RENAMED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
369
|
-
|
|
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
|