249 lines
9.3 KiB
C++
249 lines
9.3 KiB
C++
#include "core/rendering/menu.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "core/rendering/overlay.hpp"
|
|
#include "core/rendering/screen.hpp"
|
|
#include "core/rendering/text.hpp"
|
|
#include "game/options.hpp"
|
|
|
|
namespace Menu {
|
|
|
|
// --- Constants visuals ---
|
|
static constexpr int SCREEN_W = 320;
|
|
static constexpr int SCREEN_H = 200;
|
|
|
|
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 Uint8 BG_ALPHA = 220; // semi-transparent
|
|
static constexpr Uint32 BORDER_COLOR = 0xFFFFFF00; // cyan
|
|
static constexpr Uint32 TITLE_COLOR = 0xFFFFFFFF; // blanc
|
|
static constexpr Uint32 LABEL_COLOR = 0xFFCCCCCC; // gris clar
|
|
static constexpr Uint32 VALUE_COLOR = 0xFFFFFF00; // cyan
|
|
static constexpr Uint32 CURSOR_COLOR = 0xFF00FFFF; // groc
|
|
static constexpr Uint32 FOOTER_COLOR = 0xFF888888; // gris
|
|
|
|
static constexpr int TITLE_PAD_Y = 4;
|
|
static constexpr int ITEM_PAD_X = 10;
|
|
static constexpr int ITEM_SPACING = 11; // 8 px glifo + 3 pad
|
|
static constexpr int FOOTER_PAD_Y = 4;
|
|
|
|
// --- Items ---
|
|
enum class ItemKind { Toggle,
|
|
Cycle,
|
|
IntRange };
|
|
|
|
struct Item {
|
|
const char* label;
|
|
ItemKind kind;
|
|
std::function<std::string()> getValue;
|
|
std::function<void(int dir)> change; // dir: -1=left, +1=right
|
|
};
|
|
|
|
// --- Estat ---
|
|
static bool open_ = false;
|
|
static int cursor_ = 0;
|
|
static std::vector<Item> items_;
|
|
static std::unique_ptr<Text> font_;
|
|
|
|
// --- Helpers ---
|
|
|
|
static std::string yesNo(bool b) { return b ? "SI" : "NO"; }
|
|
static std::string onOff(bool b) { return b ? "ON" : "OFF"; }
|
|
|
|
// Construeix la llista d'items (Video)
|
|
static void buildItems() {
|
|
items_.clear();
|
|
|
|
// ZOOM
|
|
items_.push_back({"ZOOM", ItemKind::IntRange, [] {
|
|
char buf[16];
|
|
std::snprintf(buf, sizeof(buf), "%dX", Screen::get()->getZoom());
|
|
return std::string(buf); }, [](int dir) {
|
|
if (dir < 0) Screen::get()->decZoom();
|
|
else if (dir > 0) Screen::get()->incZoom(); }});
|
|
|
|
// PANTALLA (fullscreen)
|
|
items_.push_back({"PANTALLA", ItemKind::Toggle, [] { return std::string(Screen::get()->isFullscreen() ? "COMPLETA" : "FINESTRA"); }, [](int) { Screen::get()->toggleFullscreen(); }});
|
|
|
|
// SHADER
|
|
items_.push_back({"SHADER", ItemKind::Toggle, [] { return onOff(Options::video.shader_enabled); }, [](int) { Screen::get()->toggleShaders(); }});
|
|
|
|
// ASPECTE 4:3
|
|
items_.push_back({"ASPECTE 4:3", ItemKind::Toggle, [] { return yesNo(Options::video.aspect_ratio_4_3); }, [](int) { Screen::get()->toggleAspectRatio(); }});
|
|
|
|
// SUPERSAMPLING
|
|
items_.push_back({"SUPERSAMPLING", ItemKind::Toggle, [] { return onOff(Options::video.supersampling); }, [](int) { Screen::get()->toggleSupersampling(); }});
|
|
|
|
// TIPUS SHADER
|
|
items_.push_back({"TIPUS SHADER", ItemKind::Cycle, [] { return std::string(Screen::get()->getActiveShaderName()); }, [](int dir) {
|
|
if (dir < 0) Screen::get()->prevShaderType();
|
|
else Screen::get()->nextShaderType(); }});
|
|
|
|
// PRESET
|
|
items_.push_back({"PRESET", ItemKind::Cycle, [] { return std::string(Screen::get()->getCurrentPresetName()); }, [](int dir) {
|
|
if (dir < 0) Screen::get()->prevPreset();
|
|
else Screen::get()->nextPreset(); }});
|
|
|
|
// FILTRE 4:3
|
|
items_.push_back({"FILTRE 4:3", ItemKind::Toggle, [] { return std::string(Options::video.stretch_filter_linear ? "LINEAR" : "NEAREST"); }, [](int) { Screen::get()->toggleStretchFilter(); }});
|
|
|
|
// RENDER INFO
|
|
items_.push_back({"RENDER INFO", ItemKind::Cycle, [] {
|
|
switch (Options::render_info.position) {
|
|
case Options::RenderInfoPosition::OFF: return std::string("OFF");
|
|
case Options::RenderInfoPosition::TOP: return std::string("TOP");
|
|
case Options::RenderInfoPosition::BOTTOM: return std::string("BOTTOM");
|
|
}
|
|
return std::string("OFF"); }, [](int dir) { Overlay::cycleRenderInfo(dir); }});
|
|
}
|
|
|
|
// --- Dibuix ---
|
|
|
|
// 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) {
|
|
const Uint8 sa = src_alpha;
|
|
const Uint8 sr = src_argb & 0xFF;
|
|
const Uint8 sg = (src_argb >> 8) & 0xFF;
|
|
const Uint8 sb = (src_argb >> 16) & 0xFF;
|
|
const Uint8 inv = 255 - sa;
|
|
for (int row = y; row < y + h; row++) {
|
|
if (row < 0 || row >= SCREEN_H) continue;
|
|
for (int col = x; col < x + w; col++) {
|
|
if (col < 0 || col >= SCREEN_W) continue;
|
|
Uint32* p = &buf[col + row * SCREEN_W];
|
|
Uint32 dst = *p;
|
|
Uint8 dr = dst & 0xFF;
|
|
Uint8 dg = (dst >> 8) & 0xFF;
|
|
Uint8 db = (dst >> 16) & 0xFF;
|
|
Uint8 r = (sr * sa + dr * inv) / 255;
|
|
Uint8 g = (sg * sa + dg * inv) / 255;
|
|
Uint8 b = (sb * sa + db * inv) / 255;
|
|
*p = 0xFF000000u | (static_cast<Uint32>(b) << 16) | (static_cast<Uint32>(g) << 8) | r;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fillRect(Uint32* buf, int x, int y, int w, int h, Uint32 color) {
|
|
for (int row = y; row < y + h; row++) {
|
|
if (row < 0 || row >= SCREEN_H) continue;
|
|
for (int col = x; col < x + w; col++) {
|
|
if (col < 0 || col >= SCREEN_W) continue;
|
|
buf[col + row * SCREEN_W] = 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 + h - 1, w, 1, color); // bottom
|
|
fillRect(buf, x, y, 1, h, color); // left
|
|
fillRect(buf, x + w - 1, y, 1, h, color); // right
|
|
}
|
|
|
|
// --- API pública ---
|
|
|
|
void init() {
|
|
font_ = std::make_unique<Text>("fonts/8bithud.fnt", "fonts/8bithud.gif");
|
|
buildItems();
|
|
cursor_ = 0;
|
|
open_ = false;
|
|
}
|
|
|
|
void destroy() {
|
|
font_.reset();
|
|
items_.clear();
|
|
}
|
|
|
|
auto isOpen() -> bool {
|
|
return open_;
|
|
}
|
|
|
|
void toggle() {
|
|
open_ = !open_;
|
|
}
|
|
|
|
void close() {
|
|
open_ = false;
|
|
}
|
|
|
|
void handleKey(SDL_Scancode sc) {
|
|
if (!open_ || items_.empty()) return;
|
|
switch (sc) {
|
|
case SDL_SCANCODE_UP:
|
|
cursor_ = (cursor_ - 1 + static_cast<int>(items_.size())) % static_cast<int>(items_.size());
|
|
break;
|
|
case SDL_SCANCODE_DOWN:
|
|
cursor_ = (cursor_ + 1) % static_cast<int>(items_.size());
|
|
break;
|
|
case SDL_SCANCODE_LEFT:
|
|
items_[cursor_].change(-1);
|
|
break;
|
|
case SDL_SCANCODE_RIGHT:
|
|
case SDL_SCANCODE_RETURN:
|
|
case SDL_SCANCODE_KP_ENTER:
|
|
items_[cursor_].change(+1);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void render(Uint32* pixel_data) {
|
|
if (!open_ || !font_ || !pixel_data) return;
|
|
|
|
// Fons semi-transparent
|
|
blendRect(pixel_data, BOX_X, BOX_Y, BOX_W, BOX_H, BG_COLOR, BG_ALPHA);
|
|
|
|
// Vora
|
|
drawBorder(pixel_data, BOX_X, BOX_Y, BOX_W, BOX_H, BORDER_COLOR);
|
|
|
|
// Títol
|
|
const char* title = "OPCIONS DE VIDEO";
|
|
int title_w = font_->width(title);
|
|
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
|
|
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);
|
|
|
|
// Items
|
|
int items_y = title_line_y + 4;
|
|
for (size_t i = 0; i < items_.size(); i++) {
|
|
int y = items_y + static_cast<int>(i) * ITEM_SPACING;
|
|
bool selected = (static_cast<int>(i) == cursor_);
|
|
|
|
// Cursor
|
|
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
|
|
const char* footer = "^v:MOU <>:CANVIA ESC:IX";
|
|
int footer_w = font_->width(footer);
|
|
int footer_y = BOX_Y + BOX_H - font_->charHeight() - FOOTER_PAD_Y;
|
|
font_->draw(pixel_data, BOX_X + (BOX_W - footer_w) / 2, footer_y, footer, FOOTER_COLOR);
|
|
}
|
|
|
|
} // namespace Menu
|