#include "const.h" #include "menu.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; 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 if (file != "") { load(file); } // Deja el cursor en el primer elemento 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; } } // Carga la configuración del menu desde un archivo de texto bool Menu::load(std::string file_path) { // Indicador de éxito en la carga bool success = true; // Indica si se ha creado ya el objeto de texto bool textAllocated = false; const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1); std::string line; std::ifstream file(file_path); // El fichero se puede abrir if (file.good()) { // Procesa el fichero linea a linea printf("Reading file %s\n", filename.c_str()); 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; do { // Lee la siguiente linea std::getline(file, line); // Encuentra la posición del caracter '=' int pos = line.find("="); // Procesa las dos subcadenas if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length()))) { printf("Warning: file %s\n, unknown parameter \"%s\"\n", filename.c_str(), line.substr(0, pos).c_str()); success = false; } } while (line != "[/item]"); addItem(item.label, item.hPaddingDown, item.selectable, item.greyed, item.linkedDown); } // En caso contrario se parsea el fichero para buscar las variables y los valores else { // Encuentra la posición del caracter '=' int pos = line.find("="); // Procesa las dos subcadenas if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) { printf("Warning: file %s, unknown parameter \"%s\"\n", filename.c_str(), line.substr(0, pos).c_str()); success = false; } // Crea el objeto text tan pronto como se pueda. Necesario para añadir items if (font_png != "" && font_txt != "" && !textAllocated) { text = new Text(asset->get(font_png), asset->get(font_txt), renderer); textAllocated = true; } } } // Cierra el fichero printf("Closing file %s\n", filename.c_str()); file.close(); } // El fichero no se puede abrir else { printf("Warning: Unable to open %s file\n", filename.c_str()); success = false; } // Reorganiza el menu con los valores recien cargados // reorganize(); return success; } // 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 == "") || (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") { soundCancel = JA_LoadSound(asset->get(value).c_str()); } else if (var == "sound_accept") { soundAccept = JA_LoadSound(asset->get(value).c_str()); } else if (var == "sound_move") { soundMove = JA_LoadSound(asset->get(value).c_str()); } 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; } // Inicializa las variables void Menu::init() { } // 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; } } 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); } else { selector.rect.y = int(selector.y); } 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; } } 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() { // 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() { // 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_SetRenderDrawColor(renderer, rectBG.color.r, rectBG.color.g, rectBG.color.b, rectBG.a); SDL_RenderFillRect(renderer, &rectBG.rect); } // Renderiza el rectangulo del selector const SDL_Rect temp = {selector.rect.x, selector.rect.y - 1, selector.rect.w, selector.rect.h + 1}; SDL_SetRenderDrawColor(renderer, selector.color.r, selector.color.g, selector.color.b, selector.a); SDL_RenderFillRect(renderer, &temp); // Renderiza el borde del fondo if (backgroundType == MENU_BACKGROUND_SOLID) { SDL_SetRenderDrawColor(renderer, rectBG.color.r, rectBG.color.g, rectBG.color.b, 255); SDL_RenderDrawRect(renderer, &rectBG.rect); } // Renderiza el texto for (int i = 0; i < (int)item.size(); ++i) { if (i == selector.index) { 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 (item[i].selectable) { text->write(item[i].rect.x, item[i].rect.y, item[i].label); } else if (item[i].greyed) { text->writeColored(item[i].rect.x, item[i].rect.y, item[i].label, colorGreyed); } else { // No seleccionable if ((item[i].linkedUp) && (i == selector.index + 1)) { 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 // No enlazado con el de arriba { 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(std::string text, int hPaddingDown, bool selectable, bool greyed, bool linkedDown) { 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; temp.hPaddingDown = hPaddingDown; temp.selectable = selectable; temp.greyed = greyed; temp.linkedDown = linkedDown; item.push_back(temp); setItemCaption(item.size() - 1, text); if (item.size() > 1) { if (item.at(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.at(index).label = text; item.at(index).rect.w = this->text->lenght(item.at(index).label); item.at(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; } // 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(font_png, font_txt, renderer); } }