reestructuració

This commit is contained in:
2026-04-17 17:15:38 +02:00
parent 55caef3210
commit 5fec0110b3
66 changed files with 221 additions and 217 deletions

898
source/game/ui/menu.cpp Normal file
View File

@@ -0,0 +1,898 @@
#include "game/ui/menu.h"
#include <algorithm> // for max, min
#include <fstream> // for char_traits, basic_ifstream, basic_istream
#include <sstream> // for basic_stringstream
#include "core/audio/jail_audio.hpp" // for JA_LoadSound, JA_PlaySound, JA_DeleteSound
#include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
#include "core/rendering/text.h" // for Text
#include "core/resources/asset.h" // for Asset
#include "core/resources/resource_helper.h"
// Constructor
Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file) {
// Copia punteros
this->renderer = renderer;
this->asset = asset;
this->input = input;
// Inicializa punteros
soundMove = nullptr;
soundAccept = nullptr;
soundCancel = nullptr;
// Inicializa variables
name = "";
selector.index = 0;
selector.previousIndex = 0;
itemSelected = MENU_NO_OPTION;
x = 0;
y = 0;
w = 0;
rectBG.rect = {0, 0, 0, 0};
rectBG.color = {0, 0, 0};
rectBG.a = 0;
backgroundType = MENU_BACKGROUND_SOLID;
isCenteredOnX = false;
isCenteredOnY = false;
areElementsCenteredOnX = false;
centerX = 0;
centerY = 0;
widestItem = 0;
colorGreyed = {128, 128, 128};
defaultActionWhenCancel = 0;
font_png = "";
font_txt = "";
// Selector
selector.originY = 0;
selector.targetY = 0;
selector.despY = 0;
selector.originH = 0;
selector.targetH = 0;
selector.incH = 0;
selector.y = 0.0f;
selector.h = 0.0f;
selector.numJumps = 8;
selector.moving = false;
selector.resizing = false;
selector.rect = {0, 0, 0, 0};
selector.color = {0, 0, 0};
selector.itemColor = {0, 0, 0};
selector.a = 255;
// Inicializa las variables desde un fichero. Si no se pasa fichero, el
// llamante (p.ej. Resource::preloadAll) usará loadFromBytes después —
// y ese método ya llama a setSelectorItemColors() y reset() al final.
if (file != "") {
load(file);
setSelectorItemColors();
reset();
}
}
Menu::~Menu() {
renderer = nullptr;
asset = nullptr;
input = nullptr;
if (soundMove) {
JA_DeleteSound(soundMove);
}
if (soundAccept) {
JA_DeleteSound(soundAccept);
}
if (soundCancel) {
JA_DeleteSound(soundCancel);
}
if (text != nullptr) {
delete text;
}
}
// Parser compartido (recibe cualquier istream)
bool Menu::parseFromStream(std::istream &file, const std::string &filename) {
bool success = true;
bool textAllocated = false;
std::string line;
(void)filename;
while (std::getline(file, line)) {
if (line == "[item]") {
item_t item;
item.label = "";
item.hPaddingDown = 1;
item.selectable = true;
item.greyed = false;
item.linkedDown = false;
item.visible = true;
item.line = false;
do {
std::getline(file, line);
int pos = line.find("=");
if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
success = false;
}
} while (line != "[/item]");
addItem(item);
} else {
int pos = line.find("=");
if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
success = false;
}
// Crea el objeto text tan pronto como se pueda. Necesario para añadir items.
// Carga via ResourceHelper para que funcione tanto con pack como con filesystem.
if (font_png != "" && font_txt != "" && !textAllocated) {
auto pngBytes = ResourceHelper::loadFile(asset->get(font_png));
auto txtBytes = ResourceHelper::loadFile(asset->get(font_txt));
text = new Text(pngBytes, txtBytes, renderer);
textAllocated = true;
}
}
}
return success;
}
// Carga la configuración del menu desde un archivo de texto
bool Menu::load(std::string file_path) {
const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
std::ifstream file(file_path);
if (!file.good()) {
return false;
}
return parseFromStream(file, filename);
}
// Carga el menu desde bytes en memoria
bool Menu::loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &nameForLogs) {
if (bytes.empty()) return false;
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
std::stringstream ss(content);
bool ok = parseFromStream(ss, nameForLogs);
setSelectorItemColors();
reset();
return ok;
}
// Asigna variables a partir de dos cadenas
bool Menu::setItem(item_t *item, std::string var, std::string value) {
// Indicador de éxito en la asignación
bool success = true;
if (var == "text") {
item->label = value;
}
else if (var == "hPaddingDown") {
item->hPaddingDown = std::stoi(value);
}
else if (var == "selectable") {
item->selectable = value == "true" ? true : false;
}
else if (var == "greyed") {
item->greyed = value == "true" ? true : false;
}
else if (var == "linkedDown") {
item->linkedDown = value == "true" ? true : false;
}
else if (var == "visible") {
item->visible = value == "true" ? true : false;
}
else if (var == "line") {
item->line = value == "true" ? true : false;
}
else if ((var == "") || (var == "[/item]")) {
}
else {
success = false;
}
return success;
}
// Asigna variables a partir de dos cadenas
bool Menu::setVars(std::string var, std::string value) {
// Indicador de éxito en la asignación
bool success = true;
if (var == "font_png") {
font_png = value;
}
else if (var == "font_txt") {
font_txt = value;
}
else if (var == "sound_cancel") {
auto bytes = ResourceHelper::loadFile(asset->get(value));
if (!bytes.empty()) soundCancel = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
}
else if (var == "sound_accept") {
auto bytes = ResourceHelper::loadFile(asset->get(value));
if (!bytes.empty()) soundAccept = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
}
else if (var == "sound_move") {
auto bytes = ResourceHelper::loadFile(asset->get(value));
if (!bytes.empty()) soundMove = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
}
else if (var == "name") {
name = value;
}
else if (var == "x") {
x = std::stoi(value);
}
else if (var == "centerX") {
centerX = std::stoi(value);
}
else if (var == "centerY") {
centerY = std::stoi(value);
}
else if (var == "y") {
y = std::stoi(value);
}
else if (var == "backgroundType") {
backgroundType = std::stoi(value);
}
else if (var == "backgroundColor") {
// Se introducen los valores separados por comas en un vector
std::stringstream ss(value);
std::string tmp;
getline(ss, tmp, ',');
rectBG.color.r = std::stoi(tmp);
getline(ss, tmp, ',');
rectBG.color.g = std::stoi(tmp);
getline(ss, tmp, ',');
rectBG.color.b = std::stoi(tmp);
getline(ss, tmp, ',');
rectBG.a = std::stoi(tmp);
}
else if (var == "selector_color") {
// Se introducen los valores separados por comas en un vector
std::stringstream ss(value);
std::string tmp;
getline(ss, tmp, ',');
selector.color.r = std::stoi(tmp);
getline(ss, tmp, ',');
selector.color.g = std::stoi(tmp);
getline(ss, tmp, ',');
selector.color.b = std::stoi(tmp);
getline(ss, tmp, ',');
selector.a = std::stoi(tmp);
}
else if (var == "selector_text_color") {
// Se introducen los valores separados por comas en un vector
std::stringstream ss(value);
std::string tmp;
getline(ss, tmp, ',');
selector.itemColor.r = std::stoi(tmp);
getline(ss, tmp, ',');
selector.itemColor.g = std::stoi(tmp);
getline(ss, tmp, ',');
selector.itemColor.b = std::stoi(tmp);
}
else if (var == "areElementsCenteredOnX") {
areElementsCenteredOnX = value == "true" ? true : false;
}
else if (var == "isCenteredOnX") {
isCenteredOnX = value == "true" ? true : false;
}
else if (var == "isCenteredOnY") {
isCenteredOnY = value == "true" ? true : false;
}
else if (var == "defaultActionWhenCancel") {
defaultActionWhenCancel = std::stoi(value);
}
else if (var == "") {
}
else {
success = false;
}
return success;
}
// Carga los ficheros de audio
void Menu::loadAudioFile(std::string file, int sound) {
switch (sound) {
case SOUND_ACCEPT:
soundAccept = JA_LoadSound(file.c_str());
break;
case SOUND_CANCEL:
soundCancel = JA_LoadSound(file.c_str());
break;
case SOUND_MOVE:
soundMove = JA_LoadSound(file.c_str());
break;
default:
break;
}
}
// Obtiene el nombre del menu
std::string Menu::getName() {
return name;
}
// Obtiene el valor de la variable
int Menu::getItemSelected() {
// Al llamar a esta funcion, se obtiene el valor y se borra
const int temp = itemSelected;
itemSelected = MENU_NO_OPTION;
return temp;
}
// Actualiza la posicion y el estado del selector
void Menu::updateSelector() {
if (selector.moving) {
// Calcula el desplazamiento en Y
selector.y += selector.despY;
if (selector.despY > 0) // Va hacia abajo
{
if (selector.y > selector.targetY) // Ha llegado al destino
{
selector.originY = selector.y = selector.targetY;
selector.moving = false;
}
}
else if (selector.despY < 0) // Va hacia arriba
{
if (selector.y < selector.targetY) // Ha llegado al destino
{
selector.originY = selector.y = selector.targetY;
selector.moving = false;
}
}
selector.rect.y = int(selector.y);
// Actualiza el color del item
selector.itemColorIndex++;
selector.itemColorIndex = std::min(selector.numJumps - 1, selector.itemColorIndex);
selector.itemColor = selector.jumpItemColors[selector.itemColorIndex];
selector.previousItemColor = selector.jumpItemColors[selector.numJumps - 1 - selector.itemColorIndex];
} else {
selector.rect.y = int(selector.y);
selector.itemColorIndex = 0;
selector.itemColor = selector.jumpItemColors[selector.numJumps - 1];
selector.previousItemColor = selector.jumpItemColors[0];
}
if (selector.resizing) {
// Calcula el incremento en H
selector.h += selector.incH;
if (selector.incH > 0) // Crece
{
if (selector.h > selector.targetH) // Ha llegado al destino
{
// selector.originH = selector.targetH = selector.rect.h = getSelectorHeight(selector.index);
selector.originH = selector.h = selector.targetH;
selector.resizing = false;
}
}
else if (selector.incH < 0) // Decrece
{
if (selector.h < selector.targetH) // Ha llegado al destino
{
// selector.originH = selector.targetH = selector.rect.h = getSelectorHeight(selector.index);
selector.originH = selector.h = selector.targetH;
selector.resizing = false;
}
}
selector.rect.h = int(selector.h);
} else {
selector.rect.h = getSelectorHeight(selector.index);
}
}
// Coloca el selector en una posición específica
void Menu::setSelectorPos(int index) {
if (index < (int)item.size()) {
selector.index = index;
selector.rect.y = selector.y = selector.originY = selector.targetY = item[selector.index].rect.y;
selector.rect.w = rectBG.rect.w;
selector.rect.x = rectBG.rect.x;
selector.originH = selector.targetH = selector.rect.h = getSelectorHeight(selector.index);
selector.moving = false;
selector.resizing = false;
}
}
// Obtiene la anchura del elemento más ancho del menu
int Menu::getWidestItem() {
int result = 0;
// Obtenemos la anchura del item mas ancho
for (auto &i : item) {
result = std::max(result, i.rect.w);
}
return result;
}
// Deja el menu apuntando al primer elemento
void Menu::reset() {
itemSelected = MENU_NO_OPTION;
selector.index = 0;
selector.originY = selector.targetY = selector.y = item[0].rect.y;
selector.originH = selector.targetH = item[0].rect.h;
selector.moving = false;
selector.resizing = false;
// Si el primer elemento no es seleccionable, incrementa el selector
if (!item[selector.index].selectable) {
increaseSelectorIndex();
setSelectorPos(selector.index);
}
}
// Actualiza el menu para recolocarlo correctamente y establecer el tamaño
void Menu::reorganize() {
setRectSize();
if (isCenteredOnX) {
centerMenuOnX(centerX);
}
if (isCenteredOnY) {
centerMenuOnY(centerY);
}
if (areElementsCenteredOnX) {
centerMenuElementsOnX();
}
}
// Deja el menu apuntando al siguiente elemento
bool Menu::increaseSelectorIndex() {
// Guarda el indice actual antes de modificarlo
selector.previousIndex = selector.index;
// Obten las coordenadas del elemento actual
selector.y = selector.originY = item[selector.index].rect.y;
selector.h = selector.originH = getSelectorHeight(selector.index);
// Calcula cual es el siguiente elemento
++selector.index %= item.size();
while (!item[selector.index].selectable) {
++selector.index %= item.size();
}
// Establece las coordenadas y altura de destino
selector.targetY = item[selector.index].rect.y;
selector.despY = (selector.targetY - selector.originY) / selector.numJumps;
selector.targetH = getSelectorHeight(selector.index);
selector.incH = (selector.targetH - selector.originH) / selector.numJumps;
selector.moving = true;
if (selector.incH != 0) {
selector.resizing = true;
}
return true;
}
// Deja el menu apuntando al elemento anterior
bool Menu::decreaseSelectorIndex() {
// Guarda el indice actual antes de modificarlo
selector.previousIndex = selector.index;
// Obten las coordenadas del elemento actual
selector.y = selector.originY = item[selector.index].rect.y;
selector.h = selector.originH = getSelectorHeight(selector.index);
// Calcula cual es el siguiente elemento
if (selector.index == 0) {
selector.index = item.size() - 1;
} else {
selector.index--;
}
while (!item[selector.index].selectable) {
if (selector.index == 0) {
selector.index = item.size() - 1;
} else {
selector.index--;
}
}
// Establece las coordenadas y altura de destino
selector.targetY = item[selector.index].rect.y;
selector.despY = (selector.targetY - selector.originY) / selector.numJumps;
selector.targetH = getSelectorHeight(selector.index);
selector.incH = (selector.targetH - selector.originH) / selector.numJumps;
selector.moving = true;
if (selector.incH != 0) {
selector.resizing = true;
}
return true;
}
// Actualiza la logica del menu
void Menu::update() {
checkInput();
updateSelector();
}
// Pinta el menu en pantalla
void Menu::render() {
// Rendereritza el fondo del menu
if (backgroundType == MENU_BACKGROUND_SOLID) {
SDL_FRect fBG = {(float)rectBG.rect.x, (float)rectBG.rect.y, (float)rectBG.rect.w, (float)rectBG.rect.h};
SDL_SetRenderDrawColor(renderer, rectBG.color.r, rectBG.color.g, rectBG.color.b, rectBG.a);
SDL_RenderFillRect(renderer, &fBG);
}
// Renderiza el rectangulo del selector
const SDL_FRect fTemp = {(float)selector.rect.x, (float)(selector.rect.y - 1), (float)selector.rect.w, (float)(selector.rect.h + 1)};
SDL_SetRenderDrawColor(renderer, selector.color.r, selector.color.g, selector.color.b, selector.a);
SDL_RenderFillRect(renderer, &fTemp);
// Renderiza el borde del fondo
if (backgroundType == MENU_BACKGROUND_SOLID) {
SDL_FRect fBGBorder = {(float)rectBG.rect.x, (float)rectBG.rect.y, (float)rectBG.rect.w, (float)rectBG.rect.h};
SDL_SetRenderDrawColor(renderer, rectBG.color.r, rectBG.color.g, rectBG.color.b, 255);
SDL_RenderRect(renderer, &fBGBorder);
}
// Crea una linea por si hay que dibujarla entre los items
h_line_t line;
line.x1 = selector.rect.x + (selector.rect.w / 6);
line.x2 = line.x1 + ((selector.rect.w / 6) * 4);
// Renderiza el texto
for (int i = 0; i < (int)item.size(); ++i) {
if (item[i].visible) {
// Comprueba si ha de dibujar una linea en el elemento del menu
if (item[i].line) {
line.y = item[i].rect.y + item[i].rect.h + (item[i].hPaddingDown / 2) - 1;
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 64);
SDL_RenderLine(renderer, line.x1, line.y, line.x2, line.y);
}
// Dibuja el elemento
if (item[i].greyed) { // Tiene prioridad si el elemento es gris
text->writeColored(item[i].rect.x, item[i].rect.y, item[i].label, colorGreyed);
}
else if (i == selector.index) { // A continuación si tiene el indice
const color_t color = {selector.itemColor.r, selector.itemColor.g, selector.itemColor.b};
text->writeColored(item[i].rect.x, item[i].rect.y, item[i].label, color);
}
else if (i == selector.previousIndex) { // O si lo ha tenido
const color_t color = {selector.previousItemColor.r, selector.previousItemColor.g, selector.previousItemColor.b};
text->writeColored(item[i].rect.x, item[i].rect.y, item[i].label, color);
}
else if (item[i].selectable) { // O si simplemente es un elemento normal
text->write(item[i].rect.x, item[i].rect.y, item[i].label);
}
else { // Si no es seleccionable
if ((item[i].linkedUp) && (i == selector.index + 1)) { // Si el elemento está enlazado con el elemento superior se pinta del color del selector
const color_t color = {selector.itemColor.r, selector.itemColor.g, selector.itemColor.b};
text->writeColored(item[i].rect.x, item[i].rect.y, item[i].label, color);
} else { // Si no está enlazado con el elemento superior se pinta con el color normal
text->write(item[i].rect.x, item[i].rect.y, item[i].label);
}
}
}
}
}
// Establece el rectangulo de fondo del menu y el selector
void Menu::setRectSize(int w, int h) {
// Establece el ancho
if (w == 0) { // Si no se pasa un valor, se busca si hay uno prefijado
if (this->w == 0) { // Si no hay prefijado, coge el item mas ancho
rectBG.rect.w = findWidth() + text->getCharacterSize();
} else { // Si hay prefijado, coge ese
rectBG.rect.w = this->w;
}
} else { // Si se pasa un valor, se usa y se prefija
rectBG.rect.w = w;
this->w = w;
}
// Establece el alto
if (h == 0) { // Si no se pasa un valor, se busca de manera automatica
rectBG.rect.h = findHeight() + text->getCharacterSize();
} else { // Si se pasa un valor, se aplica
rectBG.rect.h = h;
}
// La posición X es la del menú menos medio caracter
if (this->w != 0) { // Si el ancho esta prefijado, la x coinccide
rectBG.rect.x = x;
} else { // Si el ancho es automatico, se le da un poco de margen
rectBG.rect.x = x - (text->getCharacterSize() / 2);
}
// La posición Y es la del menu menos la altura de medio caracter
rectBG.rect.y = y - (text->getCharacterSize() / 2);
// Establecemos los valores del rectangulo del selector a partir de los valores del rectangulo de fondo
setSelectorPos(selector.index);
}
// Establece el color del rectangulo de fondo
void Menu::setBackgroundColor(color_t color, int alpha) {
rectBG.color = color;
rectBG.a = alpha;
}
// Establece el color del rectangulo del selector
void Menu::setSelectorColor(color_t color, int alpha) {
selector.color = color;
selector.a = alpha;
}
// Establece el color del texto del selector
void Menu::setSelectorTextColor(color_t color) {
selector.itemColor = color;
}
// Centra el menu respecto un punto en el eje X
void Menu::centerMenuOnX(int value) {
isCenteredOnX = true;
if (value != 0) {
centerX = value;
} else if (centerX == 0) {
return;
}
// Establece la nueva posición centrada en funcion del elemento más ancho o del ancho fijo del menu
if (w != 0) { // Si se ha definido un ancho fijo
x = (centerX) - (w / 2);
} else { // Si se actua en función del elemento más ancho
x = (centerX) - (findWidth() / 2);
}
// Actualiza el rectangulo de fondo y del selector
rectBG.rect.x = x;
selector.rect.x = x;
// Reposiciona los elementos del menu
for (auto &i : item) {
i.rect.x = x;
}
// Recalcula el rectangulo de fondo
setRectSize();
// Vuelve a centrar los elementos si fuera el caso
if (areElementsCenteredOnX) {
centerMenuElementsOnX();
}
}
// Centra el menu respecto un punto en el eje Y
void Menu::centerMenuOnY(int value) {
isCenteredOnY = true;
centerY = value;
// Establece la nueva posición centrada en funcion del elemento más ancho
y = (value) - (findHeight() / 2);
// Reposiciona los elementos del menu
replaceElementsOnY();
// Recalcula el rectangulo de fondo
setRectSize();
}
// Centra los elementos del menu en el eje X
void Menu::centerMenuElementsOnX() {
areElementsCenteredOnX = true;
for (auto &i : item) {
i.rect.x = (centerX - (i.rect.w / 2));
}
}
// Añade un item al menu
void Menu::addItem(item_t temp) {
// item_t temp;
if (item.empty()) { // Si es el primer item coge la posición en el eje Y del propio menu
temp.rect.y = y;
} else { // En caso contrario, coge la posición en el eje Y a partir del último elemento
temp.rect.y = item.back().rect.y + item.back().rect.h + item.back().hPaddingDown;
}
temp.rect.x = x;
item.push_back(temp);
setItemCaption(item.size() - 1, temp.label);
if (item.size() > 1) {
if (item[item.size() - 2].linkedDown) {
item.back().linkedUp = true;
}
}
centerX = x + (findWidth() / 2);
reorganize();
}
// Cambia el texto de un item
void Menu::setItemCaption(int index, std::string text) {
item[index].label = text;
item[index].rect.w = this->text->lenght(item[index].label);
item[index].rect.h = this->text->getCharacterSize();
reorganize();
}
// Establece el indice del itemm que se usará por defecto al cancelar el menu
void Menu::setDefaultActionWhenCancel(int item) {
defaultActionWhenCancel = item;
}
// Gestiona la entrada de teclado y mando durante el menu
void Menu::checkInput() {
if (input->checkInput(input_up, REPEAT_FALSE)) {
if (decreaseSelectorIndex()) {
if (soundMove) {
JA_PlaySound(soundMove);
}
}
}
if (input->checkInput(input_down, REPEAT_FALSE)) {
if (increaseSelectorIndex()) {
if (soundMove) {
JA_PlaySound(soundMove);
}
}
}
if (input->checkInput(input_accept, REPEAT_FALSE)) {
itemSelected = selector.index;
if (soundAccept) {
JA_PlaySound(soundAccept);
}
}
if (input->checkInput(input_cancel, REPEAT_FALSE)) {
itemSelected = defaultActionWhenCancel;
if (soundCancel) {
JA_PlaySound(soundCancel);
}
}
}
// Calcula el ancho del menu
int Menu::findWidth() {
return getWidestItem();
}
// Calcula el alto del menu
int Menu::findHeight() {
int height = 0;
// Obtenemos la altura de la suma de alturas de los items
for (auto &i : item) {
height += i.rect.h + i.hPaddingDown;
}
return height - item.back().hPaddingDown;
}
// Recoloca los elementos del menu en el eje Y
void Menu::replaceElementsOnY() {
item[0].rect.y = y;
for (int i = 1; i < (int)item.size(); i++) {
item[i].rect.y = item[i - 1].rect.y + item[i - 1].rect.h + item[i - 1].hPaddingDown;
}
}
// Establece el estado seleccionable de un item
void Menu::setSelectable(int index, bool value) {
item[index].selectable = value;
}
// Establece el estado agrisado de un item
void Menu::setGreyed(int index, bool value) {
item[index].greyed = value;
}
// Establece el estado de enlace de un item
void Menu::setLinkedDown(int index, bool value) {
item[index].linkedDown = value;
}
// Establece el estado de visibilidad de un item
void Menu::setVisible(int index, bool value) {
item[index].visible = value;
}
// Calcula la altura del selector
int Menu::getSelectorHeight(int value) {
if (item[value].linkedDown) {
return item[value].rect.h + item[value].hPaddingDown + item[value + 1].rect.h;
} else {
return item[value].rect.h;
}
}
// Establece el nombre del menu
void Menu::setName(std::string name) {
this->name = name;
}
// Establece la posición del menu
void Menu::setPos(int x, int y) {
this->x = x;
this->y = y;
}
// Establece el tipo de fondo del menu
void Menu::setBackgroundType(int value) {
backgroundType = value;
}
// Establece la fuente de texto que se utilizará
void Menu::setText(std::string font_png, std::string font_txt) {
if (!text) {
text = new Text(asset->get(font_png), asset->get(font_txt), renderer);
}
}
// Calcula los colores del selector para el degradado
void Menu::setSelectorItemColors() {
const color_t colorFrom = {255, 255, 255};
const color_t colorTo = selector.itemColor;
for (int i = 0; i < selector.numJumps; ++i) {
const float step = ((float)i / (selector.numJumps - 1));
const int r = colorFrom.r + ((colorTo.r - colorFrom.r) * step);
const int g = colorFrom.g + ((colorTo.g - colorFrom.g) * step);
const int b = colorFrom.b + ((colorTo.b - colorFrom.b) * step);
selector.jumpItemColors[i].r = r;
selector.jumpItemColors[i].g = g;
selector.jumpItemColors[i].b = b;
}
selector.itemColorIndex = 0;
}

234
source/game/ui/menu.h Normal file
View File

@@ -0,0 +1,234 @@
#pragma once
#include <SDL3/SDL.h>
#include <cstdint>
#include <string> // for string, basic_string
#include <vector> // for vector
#include "utils/utils.h" // for color_t
class Asset;
class Input;
class Text;
struct JA_Sound_t;
// Tipos de fondos para el menú
constexpr int MENU_BACKGROUND_TRANSPARENT = 0;
constexpr int MENU_BACKGROUND_SOLID = 1;
// Tipos de archivos de audio
constexpr int SOUND_ACCEPT = 0;
constexpr int SOUND_MOVE = 1;
constexpr int SOUND_CANCEL = 2;
// Opciones de menú
constexpr int MENU_NO_OPTION = -1;
// Clase Menu
class Menu {
private:
struct rectangle_t {
SDL_Rect rect; // Rectangulo
color_t color; // Color
int a; // Transparencia
};
struct item_t {
std::string label; // Texto
SDL_Rect rect; // Rectangulo que delimita el elemento
int hPaddingDown; // Espaciado bajo el elemento
bool selectable; // Indica si se puede seleccionar
bool greyed; // Indica si ha de aparecer con otro color mas oscuro
bool linkedDown; // Indica si el elemento actual y el siguiente se tratan como uno solo. Afecta al selector
bool linkedUp; // Indica si el elemento actual y el anterior se tratan como uno solo. Afecta al selector
bool visible; // Indica si el elemento es visible
bool line; // Indica si el elemento lleva una linea a continuación
};
struct selector_t {
float originY; // Coordenada de origen
float targetY; // Coordenada de destino
float despY; // Cantidad de pixeles que se desplaza el selector en cada salto: (target - origin) / numJumps
bool moving; // Indica si el selector está avanzando hacia el destino
float originH; // Altura de origen
float targetH; // Altura de destino
float incH; // Cantidad de pixels que debe incrementar o decrementar el selector en cada salto
bool resizing; // Indica si el selector está cambiando de tamaño
float y; // Coordenada actual, usado para el desplazamiento
float h; // Altura actual, usado para el cambio de tamaño
int numJumps; // Numero de pasos preestablecido para llegar al destino
int index; // Elemento del menu que tiene el foco
int previousIndex; // Elemento que tenia el foco previamente
color_t previousItemColor; // Color del item nque tenia el foco previamente
SDL_Rect rect; // Rectangulo del selector
color_t color; // Color del selector
color_t itemColor; // Color del item
color_t jumpItemColors[8]; // Transición de colores para el item seleccionado
int itemColorIndex; // Indice del color de transición para el item seleccionado
int a; // Cantidad de transparencia para el rectangulo del selector
};
// Objetos y punteros
SDL_Renderer *renderer; // Puntero al renderizador de la ventana
Asset *asset; // Objeto para gestionar los ficheros de recursos
Text *text; // Texto para poder escribir los items del menu
Input *input; // Gestor de eventos de entrada de teclado o gamepad
// Variables
std::string name; // Nombre del menu
int x; // Posición en el eje X de la primera letra del primer elemento
int y; // Posición en el eje Y de la primera letra del primer elemento
int h; // Altura del menu
int w; // Anchura del menu
int itemSelected; // Índice del item del menu que ha sido seleccionado
int defaultActionWhenCancel; // Indice del item del menu que se selecciona cuando se cancela el menu
int backgroundType; // Tipo de fondo para el menu
int centerX; // Centro del menu en el eje X
int centerY; // Centro del menu en el eje Y
bool isCenteredOnX; // Variable para saber si el menu debe estar centrado respecto a un punto en el eje X
bool isCenteredOnY; // Variable para saber si el menu debe estar centrado respecto a un punto en el eje Y
bool areElementsCenteredOnX; // Variable para saber si los elementos van centrados en el eje X
int widestItem; // Anchura del elemento más ancho
JA_Sound_t *soundAccept; // Sonido al aceptar o elegir una opción del menu
JA_Sound_t *soundCancel; // Sonido al cancelar el menu
JA_Sound_t *soundMove; // Sonido al mover el selector
color_t colorGreyed; // Color para los elementos agrisados
rectangle_t rectBG; // Rectangulo de fondo del menu
std::vector<item_t> item; // Estructura para cada elemento del menu
selector_t selector; // Variables para pintar el selector del menu
std::string font_png;
std::string font_txt;
// Carga la configuración del menu desde un archivo de texto
bool load(std::string file_path);
// Parser compartido (recibe cualquier istream)
bool parseFromStream(std::istream &file, const std::string &filename);
// Asigna variables a partir de dos cadenas
bool setVars(std::string var, std::string value);
// Asigna variables a partir de dos cadenas
bool setItem(item_t *item, std::string var, std::string value);
// Actualiza el menu para recolocarlo correctamente y establecer el tamaño
void reorganize();
// Deja el menu apuntando al siguiente elemento
bool increaseSelectorIndex();
// Deja el menu apuntando al elemento anterior
bool decreaseSelectorIndex();
// Actualiza la posicion y el estado del selector
void updateSelector();
// Obtiene la anchura del elemento más ancho del menu
int getWidestItem();
// Gestiona la entrada de teclado y mando durante el menu
void checkMenuInput(Menu *menu);
// Calcula el ancho del menu
int findWidth();
// Calcula el alto del menu
int findHeight();
// Recoloca los elementos del menu en el eje Y
void replaceElementsOnY();
// Calcula la altura del selector
int getSelectorHeight(int value);
// Calcula los colores del selector para el degradado
void setSelectorItemColors();
public:
// Constructor
Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file = "");
// Destructor
~Menu();
// Carga el menu desde bytes en memoria
bool loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &nameForLogs = "");
// Carga los ficheros de audio
void loadAudioFile(std::string file, int sound);
// Obtiene el nombre del menu
std::string getName();
// Obtiene el valor de la variable
int getItemSelected();
// Deja el menu apuntando al primer elemento
void reset();
// Gestiona la entrada de teclado y mando durante el menu
void checkInput();
// Actualiza la logica del menu
void update();
// Pinta el menu en pantalla
void render();
// Establece el color del rectangulo de fondo
void setBackgroundColor(color_t color, int alpha);
// Establece el color del rectangulo del selector
void setSelectorColor(color_t color, int alpha);
// Establece el color del texto del selector
void setSelectorTextColor(color_t color);
// Centra el menu respecto a un punto en el eje X
void centerMenuOnX(int value = 0);
// Centra el menu respecto a un punto en el eje Y
void centerMenuOnY(int value);
// Centra los elementos del menu en el eje X
void centerMenuElementsOnX();
// Añade un item al menu
void addItem(item_t item);
// Cambia el texto de un item
void setItemCaption(int index, std::string text);
// Establece el indice del item que se usará por defecto al cancelar el menu
void setDefaultActionWhenCancel(int item);
// Coloca el selector en una posición específica
void setSelectorPos(int index);
// Establece el estado seleccionable de un item
void setSelectable(int index, bool value);
// Establece el estado agrisado de un item
void setGreyed(int index, bool value);
// Establece el estado de enlace de un item
void setLinkedDown(int index, bool value);
// Establece el estado de visibilidad de un item
void setVisible(int index, bool value);
// Establece el nombre del menu
void setName(std::string name);
// Establece la posición del menu
void setPos(int x, int y);
// Establece el tipo de fondo del menu
void setBackgroundType(int value);
// Establece la fuente de texto que se utilizará
void setText(std::string font_png, std::string font_txt);
// Establece el rectangulo de fondo del menu
void setRectSize(int w = 0, int h = 0);
};