menu i opcions de audio
This commit is contained in:
@@ -18,9 +18,6 @@ namespace Menu {
|
|||||||
static constexpr int SCREEN_H = 200;
|
static constexpr int SCREEN_H = 200;
|
||||||
|
|
||||||
static constexpr int BOX_W = 220;
|
static constexpr int BOX_W = 220;
|
||||||
static constexpr int BOX_H = 150;
|
|
||||||
static constexpr int BOX_X = (SCREEN_W - BOX_W) / 2; // 50
|
|
||||||
static constexpr int BOX_Y = (SCREEN_H - BOX_H) / 2; // 25
|
|
||||||
|
|
||||||
static constexpr Uint32 BG_COLOR = 0xFF1A0E0E; // fons marró fosc (ABGR)
|
static constexpr Uint32 BG_COLOR = 0xFF1A0E0E; // fons marró fosc (ABGR)
|
||||||
static constexpr Uint8 BG_ALPHA = 220; // semi-transparent
|
static constexpr Uint8 BG_ALPHA = 220; // semi-transparent
|
||||||
@@ -29,87 +26,144 @@ namespace Menu {
|
|||||||
static constexpr Uint32 LABEL_COLOR = 0xFFCCCCCC; // gris clar
|
static constexpr Uint32 LABEL_COLOR = 0xFFCCCCCC; // gris clar
|
||||||
static constexpr Uint32 VALUE_COLOR = 0xFFFFFF00; // cyan
|
static constexpr Uint32 VALUE_COLOR = 0xFFFFFF00; // cyan
|
||||||
static constexpr Uint32 CURSOR_COLOR = 0xFF00FFFF; // groc
|
static constexpr Uint32 CURSOR_COLOR = 0xFF00FFFF; // groc
|
||||||
static constexpr Uint32 FOOTER_COLOR = 0xFF888888; // gris
|
static constexpr Uint32 EMPTY_COLOR = 0xFF888888; // gris
|
||||||
|
|
||||||
static constexpr int TITLE_PAD_Y = 4;
|
static constexpr int TITLE_PAD_Y = 4;
|
||||||
static constexpr int ITEM_PAD_X = 10;
|
static constexpr int ITEM_PAD_X = 10;
|
||||||
static constexpr int ITEM_SPACING = 11; // 8 px glifo + 3 pad
|
static constexpr int ITEM_SPACING = 11;
|
||||||
static constexpr int FOOTER_PAD_Y = 4;
|
static constexpr int BOTTOM_PAD = 6;
|
||||||
|
static constexpr int HEADER_H = TITLE_PAD_Y + 8 /*charH*/ + 2 + 4; // títol + línia + gap
|
||||||
|
|
||||||
|
// --- Animació ---
|
||||||
|
static constexpr float OPEN_SPEED = 8.0F; // 1.0 / 0.125s
|
||||||
|
|
||||||
// --- Items ---
|
// --- Items ---
|
||||||
enum class ItemKind { Toggle,
|
enum class ItemKind { Toggle,
|
||||||
Cycle,
|
Cycle,
|
||||||
IntRange };
|
IntRange,
|
||||||
|
Submenu };
|
||||||
|
|
||||||
struct Item {
|
struct Item {
|
||||||
const char* label;
|
const char* label;
|
||||||
ItemKind kind;
|
ItemKind kind;
|
||||||
std::function<std::string()> getValue;
|
std::function<std::string()> getValue; // opcional
|
||||||
std::function<void(int dir)> change; // dir: -1=left, +1=right
|
std::function<void(int dir)> change; // per Toggle/Cycle/IntRange
|
||||||
|
std::function<void()> enter; // per Submenu
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Page {
|
||||||
|
const char* title;
|
||||||
|
std::vector<Item> items;
|
||||||
|
int cursor{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Estat ---
|
// --- Estat ---
|
||||||
static bool open_ = false;
|
static std::vector<Page> stack_;
|
||||||
static int cursor_ = 0;
|
|
||||||
static std::vector<Item> items_;
|
|
||||||
static std::unique_ptr<Text> font_;
|
static std::unique_ptr<Text> font_;
|
||||||
|
static float open_anim_{0.0F}; // 0 = tancat, 1 = obert
|
||||||
|
static Uint32 last_ticks_{0};
|
||||||
|
|
||||||
// --- Helpers ---
|
// --- Helpers ---
|
||||||
|
|
||||||
static std::string yesNo(bool b) { return b ? "SI" : "NO"; }
|
static std::string yesNo(bool b) { return b ? "SI" : "NO"; }
|
||||||
static std::string onOff(bool b) { return b ? "ON" : "OFF"; }
|
static std::string onOff(bool b) { return b ? "ON" : "OFF"; }
|
||||||
|
|
||||||
// Construeix la llista d'items (Video)
|
// --- Builders de pàgines ---
|
||||||
static void buildItems() {
|
|
||||||
items_.clear();
|
|
||||||
|
|
||||||
// ZOOM
|
static Page buildVideo();
|
||||||
items_.push_back({"ZOOM", ItemKind::IntRange, [] {
|
static Page buildAudio();
|
||||||
|
|
||||||
|
static Page buildRoot() {
|
||||||
|
Page p{"OPCIONS", {}, 0};
|
||||||
|
p.items.push_back({"VIDEO", ItemKind::Submenu, nullptr, nullptr, [] { stack_.push_back(buildVideo()); }});
|
||||||
|
p.items.push_back({"AUDIO", ItemKind::Submenu, nullptr, nullptr, [] { stack_.push_back(buildAudio()); }});
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Page buildVideo() {
|
||||||
|
Page p{"VIDEO", {}, 0};
|
||||||
|
|
||||||
|
p.items.push_back({"ZOOM", ItemKind::IntRange, [] {
|
||||||
char buf[16];
|
char buf[16];
|
||||||
std::snprintf(buf, sizeof(buf), "%dX", Screen::get()->getZoom());
|
std::snprintf(buf, sizeof(buf), "%dX", Screen::get()->getZoom());
|
||||||
return std::string(buf); }, [](int dir) {
|
return std::string(buf); }, [](int dir) {
|
||||||
if (dir < 0) Screen::get()->decZoom();
|
if (dir < 0) Screen::get()->decZoom();
|
||||||
else if (dir > 0) Screen::get()->incZoom(); }});
|
else if (dir > 0) Screen::get()->incZoom(); }, nullptr});
|
||||||
|
|
||||||
// PANTALLA (fullscreen)
|
p.items.push_back({"PANTALLA", ItemKind::Toggle, [] { return std::string(Screen::get()->isFullscreen() ? "COMPLETA" : "FINESTRA"); }, [](int) { Screen::get()->toggleFullscreen(); }, nullptr});
|
||||||
items_.push_back({"PANTALLA", ItemKind::Toggle, [] { return std::string(Screen::get()->isFullscreen() ? "COMPLETA" : "FINESTRA"); }, [](int) { Screen::get()->toggleFullscreen(); }});
|
|
||||||
|
|
||||||
// SHADER
|
p.items.push_back({"SHADER", ItemKind::Toggle, [] { return onOff(Options::video.shader_enabled); }, [](int) { Screen::get()->toggleShaders(); }, nullptr});
|
||||||
items_.push_back({"SHADER", ItemKind::Toggle, [] { return onOff(Options::video.shader_enabled); }, [](int) { Screen::get()->toggleShaders(); }});
|
|
||||||
|
|
||||||
// ASPECTE 4:3
|
p.items.push_back({"ASPECTE 4:3", ItemKind::Toggle, [] { return yesNo(Options::video.aspect_ratio_4_3); }, [](int) { Screen::get()->toggleAspectRatio(); }, nullptr});
|
||||||
items_.push_back({"ASPECTE 4:3", ItemKind::Toggle, [] { return yesNo(Options::video.aspect_ratio_4_3); }, [](int) { Screen::get()->toggleAspectRatio(); }});
|
|
||||||
|
|
||||||
// SUPERSAMPLING
|
p.items.push_back({"SUPERSAMPLING", ItemKind::Toggle, [] { return onOff(Options::video.supersampling); }, [](int) { Screen::get()->toggleSupersampling(); }, nullptr});
|
||||||
items_.push_back({"SUPERSAMPLING", ItemKind::Toggle, [] { return onOff(Options::video.supersampling); }, [](int) { Screen::get()->toggleSupersampling(); }});
|
|
||||||
|
|
||||||
// TIPUS SHADER
|
p.items.push_back({"TIPUS SHADER", ItemKind::Cycle, [] { return std::string(Screen::get()->getActiveShaderName()); }, [](int dir) {
|
||||||
items_.push_back({"TIPUS SHADER", ItemKind::Cycle, [] { return std::string(Screen::get()->getActiveShaderName()); }, [](int dir) {
|
|
||||||
if (dir < 0) Screen::get()->prevShaderType();
|
if (dir < 0) Screen::get()->prevShaderType();
|
||||||
else Screen::get()->nextShaderType(); }});
|
else Screen::get()->nextShaderType(); }, nullptr});
|
||||||
|
|
||||||
// PRESET
|
p.items.push_back({"PRESET", ItemKind::Cycle, [] { return std::string(Screen::get()->getCurrentPresetName()); }, [](int dir) {
|
||||||
items_.push_back({"PRESET", ItemKind::Cycle, [] { return std::string(Screen::get()->getCurrentPresetName()); }, [](int dir) {
|
|
||||||
if (dir < 0) Screen::get()->prevPreset();
|
if (dir < 0) Screen::get()->prevPreset();
|
||||||
else Screen::get()->nextPreset(); }});
|
else Screen::get()->nextPreset(); }, nullptr});
|
||||||
|
|
||||||
// FILTRE 4:3
|
p.items.push_back({"FILTRE 4:3", ItemKind::Toggle, [] { return std::string(Options::video.stretch_filter_linear ? "LINEAR" : "NEAREST"); }, [](int) { Screen::get()->toggleStretchFilter(); }, nullptr});
|
||||||
items_.push_back({"FILTRE 4:3", ItemKind::Toggle, [] { return std::string(Options::video.stretch_filter_linear ? "LINEAR" : "NEAREST"); }, [](int) { Screen::get()->toggleStretchFilter(); }});
|
|
||||||
|
|
||||||
// RENDER INFO
|
p.items.push_back({"RENDER INFO", ItemKind::Cycle, [] {
|
||||||
items_.push_back({"RENDER INFO", ItemKind::Cycle, [] {
|
|
||||||
switch (Options::render_info.position) {
|
switch (Options::render_info.position) {
|
||||||
case Options::RenderInfoPosition::OFF: return std::string("OFF");
|
case Options::RenderInfoPosition::OFF: return std::string("OFF");
|
||||||
case Options::RenderInfoPosition::TOP: return std::string("TOP");
|
case Options::RenderInfoPosition::TOP: return std::string("TOP");
|
||||||
case Options::RenderInfoPosition::BOTTOM: return std::string("BOTTOM");
|
case Options::RenderInfoPosition::BOTTOM: return std::string("BOTTOM");
|
||||||
}
|
}
|
||||||
return std::string("OFF"); }, [](int dir) { Overlay::cycleRenderInfo(dir); }});
|
return std::string("OFF"); }, [](int dir) { Overlay::cycleRenderInfo(dir); }, nullptr});
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converteix volum 0..1 a percentatge i ho formata com "50%"
|
||||||
|
static std::string volPct(float v) {
|
||||||
|
int pct = static_cast<int>(v * 100.0F + 0.5F);
|
||||||
|
if (pct < 0) pct = 0;
|
||||||
|
if (pct > 100) pct = 100;
|
||||||
|
char buf[8];
|
||||||
|
std::snprintf(buf, sizeof(buf), "%d%%", pct);
|
||||||
|
return std::string(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canvi +/- d'un volum en steps de 0.05 (5%) amb clamping
|
||||||
|
static void stepVolume(float& v, int dir) {
|
||||||
|
v += (dir >= 0 ? 0.05F : -0.05F);
|
||||||
|
if (v < 0.0F) v = 0.0F;
|
||||||
|
if (v > 1.0F) v = 1.0F;
|
||||||
|
Options::applyAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Page buildAudio() {
|
||||||
|
Page p{"AUDIO", {}, 0};
|
||||||
|
|
||||||
|
p.items.push_back({"AUDIO", ItemKind::Toggle, [] { return onOff(Options::audio.enabled); }, [](int) {
|
||||||
|
Options::audio.enabled = !Options::audio.enabled;
|
||||||
|
Options::applyAudio(); }, nullptr});
|
||||||
|
|
||||||
|
p.items.push_back({"MASTER", ItemKind::IntRange, [] { return volPct(Options::audio.volume); }, [](int dir) { stepVolume(Options::audio.volume, dir); }, nullptr});
|
||||||
|
|
||||||
|
p.items.push_back({"MUSICA", ItemKind::Toggle, [] { return onOff(Options::audio.music_enabled); }, [](int) {
|
||||||
|
Options::audio.music_enabled = !Options::audio.music_enabled;
|
||||||
|
Options::applyAudio(); }, nullptr});
|
||||||
|
|
||||||
|
p.items.push_back({"VOL MUSICA", ItemKind::IntRange, [] { return volPct(Options::audio.music_volume); }, [](int dir) { stepVolume(Options::audio.music_volume, dir); }, nullptr});
|
||||||
|
|
||||||
|
p.items.push_back({"SONS", ItemKind::Toggle, [] { return onOff(Options::audio.sound_enabled); }, [](int) {
|
||||||
|
Options::audio.sound_enabled = !Options::audio.sound_enabled;
|
||||||
|
Options::applyAudio(); }, nullptr});
|
||||||
|
|
||||||
|
p.items.push_back({"VOL SONS", ItemKind::IntRange, [] { return volPct(Options::audio.sound_volume); }, [](int dir) { stepVolume(Options::audio.sound_volume, dir); }, nullptr});
|
||||||
|
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Dibuix ---
|
// --- Dibuix ---
|
||||||
|
|
||||||
// Alpha blending per pixel sobre el buffer ARGB (ABGR en memòria)
|
// Alpha blending per pixel sobre el buffer ARGB (ABGR en memòria)
|
||||||
// src_argb és el color desitjat (canal alpha ignorat, s'usa src_alpha)
|
|
||||||
static void blendRect(Uint32* buf, int x, int y, int w, int h, Uint32 src_argb, Uint8 src_alpha) {
|
static void blendRect(Uint32* buf, int x, int y, int w, int h, Uint32 src_argb, Uint8 src_alpha) {
|
||||||
const Uint8 sa = src_alpha;
|
const Uint8 sa = src_alpha;
|
||||||
const Uint8 sr = src_argb & 0xFF;
|
const Uint8 sr = src_argb & 0xFF;
|
||||||
@@ -144,54 +198,94 @@ namespace Menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void drawBorder(Uint32* buf, int x, int y, int w, int h, Uint32 color) {
|
static void drawBorder(Uint32* buf, int x, int y, int w, int h, Uint32 color) {
|
||||||
fillRect(buf, x, y, w, 1, color); // top
|
fillRect(buf, x, y, w, 1, color);
|
||||||
fillRect(buf, x, y + h - 1, w, 1, color); // bottom
|
fillRect(buf, x, y + h - 1, w, 1, color);
|
||||||
fillRect(buf, x, y, 1, h, color); // left
|
fillRect(buf, x, y, 1, h, color);
|
||||||
fillRect(buf, x + w - 1, y, 1, h, color); // right
|
fillRect(buf, x + w - 1, y, 1, h, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mida final de la caixa segons el nombre d'items
|
||||||
|
static int boxHeight(const Page& page) {
|
||||||
|
int n = static_cast<int>(page.items.size());
|
||||||
|
int body = (n == 0) ? ITEM_SPACING : n * ITEM_SPACING;
|
||||||
|
return HEADER_H + body + BOTTOM_PAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API pública ---
|
// --- API pública ---
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
font_ = std::make_unique<Text>("fonts/8bithud.fnt", "fonts/8bithud.gif");
|
font_ = std::make_unique<Text>("fonts/8bithud.fnt", "fonts/8bithud.gif");
|
||||||
buildItems();
|
stack_.clear();
|
||||||
cursor_ = 0;
|
open_anim_ = 0.0F;
|
||||||
open_ = false;
|
last_ticks_ = SDL_GetTicks();
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy() {
|
void destroy() {
|
||||||
font_.reset();
|
font_.reset();
|
||||||
items_.clear();
|
stack_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto isOpen() -> bool {
|
auto isOpen() -> bool {
|
||||||
return open_;
|
return !stack_.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggle() {
|
void toggle() {
|
||||||
open_ = !open_;
|
if (isOpen()) {
|
||||||
|
close();
|
||||||
|
} else {
|
||||||
|
stack_.push_back(buildRoot());
|
||||||
|
open_anim_ = 0.0F;
|
||||||
|
last_ticks_ = SDL_GetTicks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
open_ = false;
|
stack_.clear();
|
||||||
|
open_anim_ = 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleKey(SDL_Scancode sc) {
|
void handleKey(SDL_Scancode sc) {
|
||||||
if (!open_ || items_.empty()) return;
|
if (!isOpen()) return;
|
||||||
|
Page& page = stack_.back();
|
||||||
|
if (page.items.empty()) {
|
||||||
|
// Pàgina buida — només backspace surt
|
||||||
|
if (sc == SDL_SCANCODE_BACKSPACE) {
|
||||||
|
stack_.pop_back();
|
||||||
|
if (stack_.empty()) close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int n = static_cast<int>(page.items.size());
|
||||||
switch (sc) {
|
switch (sc) {
|
||||||
case SDL_SCANCODE_UP:
|
case SDL_SCANCODE_UP:
|
||||||
cursor_ = (cursor_ - 1 + static_cast<int>(items_.size())) % static_cast<int>(items_.size());
|
page.cursor = (page.cursor - 1 + n) % n;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_DOWN:
|
case SDL_SCANCODE_DOWN:
|
||||||
cursor_ = (cursor_ + 1) % static_cast<int>(items_.size());
|
page.cursor = (page.cursor + 1) % n;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_LEFT:
|
case SDL_SCANCODE_LEFT:
|
||||||
items_[cursor_].change(-1);
|
if (page.items[page.cursor].kind != ItemKind::Submenu &&
|
||||||
|
page.items[page.cursor].change) {
|
||||||
|
page.items[page.cursor].change(-1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_RIGHT:
|
case SDL_SCANCODE_RIGHT:
|
||||||
|
if (page.items[page.cursor].kind != ItemKind::Submenu &&
|
||||||
|
page.items[page.cursor].change) {
|
||||||
|
page.items[page.cursor].change(+1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case SDL_SCANCODE_RETURN:
|
case SDL_SCANCODE_RETURN:
|
||||||
case SDL_SCANCODE_KP_ENTER:
|
case SDL_SCANCODE_KP_ENTER:
|
||||||
items_[cursor_].change(+1);
|
if (page.items[page.cursor].kind == ItemKind::Submenu) {
|
||||||
|
if (page.items[page.cursor].enter) page.items[page.cursor].enter();
|
||||||
|
} else if (page.items[page.cursor].change) {
|
||||||
|
page.items[page.cursor].change(+1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_BACKSPACE:
|
||||||
|
stack_.pop_back();
|
||||||
|
if (stack_.empty()) close();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -199,50 +293,83 @@ namespace Menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void render(Uint32* pixel_data) {
|
void render(Uint32* pixel_data) {
|
||||||
if (!open_ || !font_ || !pixel_data) return;
|
if (!isOpen() || !font_ || !pixel_data) return;
|
||||||
|
|
||||||
// Fons semi-transparent
|
// Actualitza animació d'obertura
|
||||||
blendRect(pixel_data, BOX_X, BOX_Y, BOX_W, BOX_H, BG_COLOR, BG_ALPHA);
|
Uint32 now = SDL_GetTicks();
|
||||||
|
float dt = static_cast<float>(now - last_ticks_) / 1000.0F;
|
||||||
|
last_ticks_ = now;
|
||||||
|
if (open_anim_ < 1.0F) {
|
||||||
|
open_anim_ += OPEN_SPEED * dt;
|
||||||
|
if (open_anim_ > 1.0F) open_anim_ = 1.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page& page = stack_.back();
|
||||||
|
const int target_h = boxHeight(page);
|
||||||
|
|
||||||
|
// Ease-out quadratic: f(t) = 1 - (1-t)^2
|
||||||
|
float t = open_anim_;
|
||||||
|
float eased = 1.0F - (1.0F - t) * (1.0F - t);
|
||||||
|
|
||||||
|
// Caixa creix verticalment des del centre
|
||||||
|
int box_h = static_cast<int>(target_h * eased);
|
||||||
|
if (box_h < 2) box_h = 2;
|
||||||
|
int box_x = (SCREEN_W - BOX_W) / 2;
|
||||||
|
int box_y = (SCREEN_H - box_h) / 2;
|
||||||
|
|
||||||
|
// Fons semi-transparent (alpha escalat per l'animació)
|
||||||
|
Uint8 alpha = static_cast<Uint8>(BG_ALPHA * eased);
|
||||||
|
blendRect(pixel_data, box_x, box_y, BOX_W, box_h, BG_COLOR, alpha);
|
||||||
|
|
||||||
// Vora
|
// Vora
|
||||||
drawBorder(pixel_data, BOX_X, BOX_Y, BOX_W, BOX_H, BORDER_COLOR);
|
drawBorder(pixel_data, box_x, box_y, BOX_W, box_h, BORDER_COLOR);
|
||||||
|
|
||||||
|
// El contingut només apareix quan la caixa és prou gran
|
||||||
|
if (open_anim_ < 0.9F) return;
|
||||||
|
|
||||||
// Títol
|
// Títol
|
||||||
const char* title = "OPCIONS DE VIDEO";
|
int title_w = font_->width(page.title);
|
||||||
int title_w = font_->width(title);
|
font_->draw(pixel_data, box_x + (BOX_W - title_w) / 2, box_y + TITLE_PAD_Y, page.title, TITLE_COLOR);
|
||||||
font_->draw(pixel_data, BOX_X + (BOX_W - title_w) / 2, BOX_Y + TITLE_PAD_Y, title, TITLE_COLOR);
|
|
||||||
|
|
||||||
// Línia sota el títol
|
// Línia sota el títol
|
||||||
int title_line_y = BOX_Y + TITLE_PAD_Y + font_->charHeight() + 2;
|
int title_line_y = box_y + TITLE_PAD_Y + font_->charHeight() + 2;
|
||||||
fillRect(pixel_data, BOX_X + 4, title_line_y, BOX_W - 8, 1, BORDER_COLOR);
|
fillRect(pixel_data, box_x + 4, title_line_y, BOX_W - 8, 1, BORDER_COLOR);
|
||||||
|
|
||||||
// Items
|
// Items
|
||||||
int items_y = title_line_y + 4;
|
int items_y = title_line_y + 4;
|
||||||
for (size_t i = 0; i < items_.size(); i++) {
|
if (page.items.empty()) {
|
||||||
int y = items_y + static_cast<int>(i) * ITEM_SPACING;
|
const char* empty_text = "(BUIT)";
|
||||||
bool selected = (static_cast<int>(i) == cursor_);
|
int ew = font_->width(empty_text);
|
||||||
|
font_->draw(pixel_data, box_x + (BOX_W - ew) / 2, items_y + 2, empty_text, EMPTY_COLOR);
|
||||||
// Cursor
|
return;
|
||||||
if (selected) {
|
|
||||||
font_->draw(pixel_data, BOX_X + 4, y, ">", CURSOR_COLOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Label
|
|
||||||
Uint32 label_color = selected ? CURSOR_COLOR : LABEL_COLOR;
|
|
||||||
font_->draw(pixel_data, BOX_X + ITEM_PAD_X, y, items_[i].label, label_color);
|
|
||||||
|
|
||||||
// Valor (dreta)
|
|
||||||
std::string value = items_[i].getValue();
|
|
||||||
int value_w = font_->width(value.c_str());
|
|
||||||
Uint32 value_color = selected ? CURSOR_COLOR : VALUE_COLOR;
|
|
||||||
font_->draw(pixel_data, BOX_X + BOX_W - ITEM_PAD_X - value_w, y, value.c_str(), value_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peu
|
for (size_t i = 0; i < page.items.size(); i++) {
|
||||||
const char* footer = "^v:MOU <>:CANVIA ESC:IX";
|
int y = items_y + static_cast<int>(i) * ITEM_SPACING;
|
||||||
int footer_w = font_->width(footer);
|
bool selected = (static_cast<int>(i) == page.cursor);
|
||||||
int footer_y = BOX_Y + BOX_H - font_->charHeight() - FOOTER_PAD_Y;
|
const Item& item = page.items[i];
|
||||||
font_->draw(pixel_data, BOX_X + (BOX_W - footer_w) / 2, footer_y, footer, FOOTER_COLOR);
|
|
||||||
|
if (selected) {
|
||||||
|
font_->draw(pixel_data, box_x + 4, y, ">", CURSOR_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 label_color = selected ? CURSOR_COLOR : LABEL_COLOR;
|
||||||
|
font_->draw(pixel_data, box_x + ITEM_PAD_X, y, item.label, label_color);
|
||||||
|
|
||||||
|
// Valor (dreta) — només per items no-submenu
|
||||||
|
if (item.kind == ItemKind::Submenu) {
|
||||||
|
// Fletxa indicant que entra a un submenú
|
||||||
|
const char* arrow = ">>";
|
||||||
|
int aw = font_->width(arrow);
|
||||||
|
Uint32 ac = selected ? CURSOR_COLOR : VALUE_COLOR;
|
||||||
|
font_->draw(pixel_data, box_x + BOX_W - ITEM_PAD_X - aw, y, arrow, ac);
|
||||||
|
} else if (item.getValue) {
|
||||||
|
std::string value = item.getValue();
|
||||||
|
int value_w = font_->width(value.c_str());
|
||||||
|
Uint32 value_color = selected ? CURSOR_COLOR : VALUE_COLOR;
|
||||||
|
font_->draw(pixel_data, box_x + BOX_W - ITEM_PAD_X - value_w, y, value.c_str(), value_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Menu
|
} // namespace Menu
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace Defaults::Video {
|
|||||||
} // namespace Defaults::Video
|
} // namespace Defaults::Video
|
||||||
|
|
||||||
namespace Defaults::Audio {
|
namespace Defaults::Audio {
|
||||||
|
constexpr bool ENABLED = true;
|
||||||
constexpr float VOLUME = 1.0F;
|
constexpr float VOLUME = 1.0F;
|
||||||
constexpr bool MUSIC_ENABLED = true;
|
constexpr bool MUSIC_ENABLED = true;
|
||||||
constexpr float MUSIC_VOLUME = 0.8F;
|
constexpr float MUSIC_VOLUME = 0.8F;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/jail/jail_audio.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
#include "game/defaults.hpp"
|
#include "game/defaults.hpp"
|
||||||
#include "game/defines.hpp"
|
#include "game/defines.hpp"
|
||||||
@@ -15,12 +16,23 @@ namespace Options {
|
|||||||
config_file_path = path;
|
config_file_path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void applyAudio() {
|
||||||
|
const float master = audio.enabled ? audio.volume : 0.0F;
|
||||||
|
JA_EnableMusic(audio.music_enabled);
|
||||||
|
JA_EnableSound(audio.sound_enabled);
|
||||||
|
JA_SetMusicVolume(master * audio.music_volume);
|
||||||
|
JA_SetSoundVolume(master * audio.sound_volume);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Funcions helper de càrrega ---
|
// --- Funcions helper de càrrega ---
|
||||||
|
|
||||||
static void loadAudioConfigFromYaml(const fkyaml::node& yaml) {
|
static void loadAudioConfigFromYaml(const fkyaml::node& yaml) {
|
||||||
if (!yaml.contains("audio")) return;
|
if (!yaml.contains("audio")) return;
|
||||||
const auto& node = yaml["audio"];
|
const auto& node = yaml["audio"];
|
||||||
|
|
||||||
|
if (node.contains("enabled"))
|
||||||
|
audio.enabled = node["enabled"].get_value<bool>();
|
||||||
|
|
||||||
if (node.contains("volume"))
|
if (node.contains("volume"))
|
||||||
audio.volume = node["volume"].get_value<float>();
|
audio.volume = node["volume"].get_value<float>();
|
||||||
|
|
||||||
@@ -219,6 +231,7 @@ namespace Options {
|
|||||||
// AUDIO
|
// AUDIO
|
||||||
file << "# AUDIO\n";
|
file << "# AUDIO\n";
|
||||||
file << "audio:\n";
|
file << "audio:\n";
|
||||||
|
file << " enabled: " << (audio.enabled ? "true" : "false") << "\n";
|
||||||
file << " volume: " << audio.volume << "\n";
|
file << " volume: " << audio.volume << "\n";
|
||||||
file << " music:\n";
|
file << " music:\n";
|
||||||
file << " enabled: " << (audio.music_enabled ? "true" : "false") << "\n";
|
file << " enabled: " << (audio.music_enabled ? "true" : "false") << "\n";
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ namespace Options {
|
|||||||
|
|
||||||
// Opcions d'àudio
|
// Opcions d'àudio
|
||||||
struct Audio {
|
struct Audio {
|
||||||
|
bool enabled{Defaults::Audio::ENABLED}; // master enable
|
||||||
bool music_enabled{Defaults::Audio::MUSIC_ENABLED};
|
bool music_enabled{Defaults::Audio::MUSIC_ENABLED};
|
||||||
float music_volume{Defaults::Audio::MUSIC_VOLUME};
|
float music_volume{Defaults::Audio::MUSIC_VOLUME};
|
||||||
bool sound_enabled{Defaults::Audio::SOUND_ENABLED};
|
bool sound_enabled{Defaults::Audio::SOUND_ENABLED};
|
||||||
@@ -144,4 +145,8 @@ namespace Options {
|
|||||||
void setCrtPiFile(const std::string& path);
|
void setCrtPiFile(const std::string& path);
|
||||||
auto loadCrtPiFromFile() -> bool;
|
auto loadCrtPiFromFile() -> bool;
|
||||||
|
|
||||||
|
// Sincronitza Options::audio → jail_audio (aplica volums i enables).
|
||||||
|
// Volum efectiu = master * volum_individual per a música i sons.
|
||||||
|
void applyAudio();
|
||||||
|
|
||||||
} // namespace Options
|
} // namespace Options
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ int main(int /*argc*/, char* /*args*/[]) {
|
|||||||
Screen::init();
|
Screen::init();
|
||||||
JD8_Init();
|
JD8_Init();
|
||||||
JA_Init(48000, SDL_AUDIO_S16, 2);
|
JA_Init(48000, SDL_AUDIO_S16, 2);
|
||||||
|
Options::applyAudio();
|
||||||
Overlay::init();
|
Overlay::init();
|
||||||
Menu::init();
|
Menu::init();
|
||||||
Director::init();
|
Director::init();
|
||||||
|
|||||||
Reference in New Issue
Block a user