primera versio de menu
This commit is contained in:
@@ -14,6 +14,12 @@ void JI_DisableKeyboard(Uint32 time) {
|
||||
waitTime = time;
|
||||
}
|
||||
|
||||
static bool input_blocked = false;
|
||||
|
||||
void JI_SetInputBlocked(bool blocked) {
|
||||
input_blocked = blocked;
|
||||
}
|
||||
|
||||
void JI_moveCheats(Uint8 new_key) {
|
||||
cheat[0] = cheat[1];
|
||||
cheat[1] = cheat[2];
|
||||
@@ -37,6 +43,8 @@ void JI_Update() {
|
||||
|
||||
bool JI_KeyPressed(int key) {
|
||||
if (waitTime > 0 || keystates == nullptr) return false;
|
||||
// Input bloquejat (p.ex. menú flotant obert)
|
||||
if (input_blocked) return false;
|
||||
// ESC bloquejada pel Director (primera pulsació mostra notificació)
|
||||
if (key == SDL_SCANCODE_ESCAPE && Director::get()->isEscBlocked()) return false;
|
||||
return keystates[key] != 0;
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
void JI_DisableKeyboard(Uint32 time);
|
||||
|
||||
// Bloqueja tot l'input cap al joc (JI_KeyPressed retorna false per a tot)
|
||||
void JI_SetInputBlocked(bool blocked);
|
||||
|
||||
void JI_Update();
|
||||
|
||||
bool JI_KeyPressed(int key);
|
||||
|
||||
265
source/core/rendering/menu.cpp
Normal file
265
source/core/rendering/menu.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
#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) { Screen::get()->nextShaderType(); }});
|
||||
|
||||
// PRESET
|
||||
items_.push_back({"PRESET", ItemKind::Cycle,
|
||||
[] { return std::string(Screen::get()->getCurrentPresetName()); },
|
||||
[](int) { 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) { Overlay::toggleRenderInfo(); }});
|
||||
}
|
||||
|
||||
// --- 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
|
||||
18
source/core/rendering/menu.hpp
Normal file
18
source/core/rendering/menu.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace Menu {
|
||||
void init();
|
||||
void destroy();
|
||||
|
||||
[[nodiscard]] auto isOpen() -> bool;
|
||||
void toggle();
|
||||
void close();
|
||||
|
||||
// Pinta el menú sobre el buffer ARGB — cridat des d'Overlay::render si està obert
|
||||
void render(Uint32* pixel_data);
|
||||
|
||||
// Gestió d'input — cridat des del Director en KEY_DOWN
|
||||
void handleKey(SDL_Scancode sc);
|
||||
} // namespace Menu
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/rendering/menu.hpp"
|
||||
#include "core/rendering/text.hpp"
|
||||
#include "game/options.hpp"
|
||||
|
||||
@@ -149,6 +150,11 @@ namespace Overlay {
|
||||
if (esc_waiting_ && notifications_.empty()) {
|
||||
esc_waiting_ = false;
|
||||
}
|
||||
|
||||
// Menú flotant per damunt de tot
|
||||
if (Menu::isOpen()) {
|
||||
Menu::render(pixel_data);
|
||||
}
|
||||
}
|
||||
|
||||
void showNotification(const char* text, float duration_seconds) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "core/input/global_inputs.hpp"
|
||||
#include "core/input/mouse.hpp"
|
||||
#include "core/jail/jgame.hpp"
|
||||
#include "core/jail/jinput.hpp"
|
||||
#include "core/rendering/menu.hpp"
|
||||
#include "core/rendering/overlay.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "game/info.hpp"
|
||||
@@ -106,6 +108,26 @@ void Director::handleEvents() {
|
||||
JG_QuitSignal();
|
||||
requestQuit();
|
||||
}
|
||||
// Menú: F12 (o tecla configurada) obre/tanca el menú flotant
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == Options::keys_gui.menu_toggle) {
|
||||
Menu::toggle();
|
||||
JI_SetInputBlocked(Menu::isOpen());
|
||||
continue;
|
||||
}
|
||||
// Si el menú està obert, consumeix tot l'input de teclat
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
Menu::close();
|
||||
JI_SetInputBlocked(false);
|
||||
} else {
|
||||
Menu::handleKey(event.key.scancode);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {
|
||||
continue; // no deixem passar KEY_UP al joc tampoc
|
||||
}
|
||||
// ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) {
|
||||
esc_blocked_ = true; // Bloqueja ESC per polling immediatament
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Defaults::KeysGUI {
|
||||
constexpr SDL_Scancode NEXT_SHADER_PRESET = SDL_SCANCODE_F8;
|
||||
constexpr SDL_Scancode TOGGLE_STRETCH_FILTER = SDL_SCANCODE_F9;
|
||||
constexpr SDL_Scancode TOGGLE_RENDER_INFO = SDL_SCANCODE_F10;
|
||||
constexpr SDL_Scancode MENU_TOGGLE = SDL_SCANCODE_F12;
|
||||
} // namespace Defaults::KeysGUI
|
||||
|
||||
// Tecles de joc (moviment del personatge, accions)
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Options {
|
||||
SDL_Scancode next_shader_preset{Defaults::KeysGUI::NEXT_SHADER_PRESET};
|
||||
SDL_Scancode toggle_stretch_filter{Defaults::KeysGUI::TOGGLE_STRETCH_FILTER};
|
||||
SDL_Scancode toggle_render_info{Defaults::KeysGUI::TOGGLE_RENDER_INFO};
|
||||
SDL_Scancode menu_toggle{Defaults::KeysGUI::MENU_TOGGLE};
|
||||
};
|
||||
|
||||
// Tecles de joc (moviment, accions)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "core/jail/jdraw8.hpp"
|
||||
#include "core/jail/jfile.hpp"
|
||||
#include "core/jail/jgame.hpp"
|
||||
#include "core/rendering/menu.hpp"
|
||||
#include "core/rendering/overlay.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/system/director.hpp"
|
||||
@@ -29,6 +30,7 @@ int main(int /*argc*/, char* /*args*/[]) {
|
||||
JD8_Init();
|
||||
JA_Init(48000, SDL_AUDIO_S16, 2);
|
||||
Overlay::init();
|
||||
Menu::init();
|
||||
Director::init();
|
||||
|
||||
// Arranca el Director: crea game thread, bucle principal, sincronització de frames
|
||||
@@ -37,6 +39,7 @@ int main(int /*argc*/, char* /*args*/[]) {
|
||||
Options::saveToFile();
|
||||
|
||||
Director::destroy();
|
||||
Menu::destroy();
|
||||
Overlay::destroy();
|
||||
JA_Quit();
|
||||
JD8_Quit();
|
||||
|
||||
Reference in New Issue
Block a user