Compare commits

...

2 Commits

11 changed files with 214 additions and 14 deletions

View File

@@ -92,6 +92,15 @@
"[SERVICE_MENU] HARD": "Dificil",
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Reiniciar per aplicar canvis",
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Permetre apagar el sistema",
"[SERVICE_MENU] CONTROLS": "Controls",
"[SERVICE_MENU] KEYBOARD": "Teclat",
"[SERVICE_MENU] PLAYER1": "Jugador 1",
"[SERVICE_MENU] PLAYER2": "Jugador 2",
"[SERVICE_MENU] CONTROLLER1": "Mando 1",
"[SERVICE_MENU] CONTROLLER2": "Mando 2",
"[SERVICE_MENU] CONFIGURE1": "Configurar Mando 1",
"[SERVICE_MENU] CONFIGURE2": "Configurar Mando 2",
"[SERVICE_MENU] NO_CONTROLLER": "Cap",
"[SCOREBOARD] 1": "Jugador 1",
"[SCOREBOARD] 2": "Jugador 2",

View File

@@ -92,6 +92,16 @@
"[SERVICE_MENU] HARD": "Hard",
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Restart to apply changes",
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Enable shutdown",
"[SERVICE_MENU] CONTROLS": "Controls",
"[SERVICE_MENU] KEYBOARD": "Keyboard",
"[SERVICE_MENU] PLAYER1": "Player 1",
"[SERVICE_MENU] PLAYER2": "Player 2",
"[SERVICE_MENU] CONTROLLER1": "Controller 1",
"[SERVICE_MENU] CONTROLLER2": "Controller 2",
"[SERVICE_MENU] CONFIGURE1": "Configure Controller 1",
"[SERVICE_MENU] CONFIGURE2": "Configure Controller 2",
"[SERVICE_MENU] NO_CONTROLLER": "None",
"[SCOREBOARD] 1": "Player 1",
"[SCOREBOARD] 2": "Player 2",

View File

@@ -92,6 +92,15 @@
"[SERVICE_MENU] HARD": "Dificil",
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Reiniciar para aplicar cambios",
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Permitir apagar el sistema",
"[SERVICE_MENU] CONTROLS": "Controles",
"[SERVICE_MENU] KEYBOARD": "Teclado",
"[SERVICE_MENU] PLAYER1": "Jugador 1",
"[SERVICE_MENU] PLAYER2": "Jugador 2",
"[SERVICE_MENU] CONTROLLER1": "Mando 1",
"[SERVICE_MENU] CONTROLLER2": "Mando 2",
"[SERVICE_MENU] CONFIGURE1": "Configurar Mando 1",
"[SERVICE_MENU] CONFIGURE2": "Configurar Mando 2",
"[SERVICE_MENU] NO_CONTROLLER": "Ninguno",
"[SCOREBOARD] 1": "Jugador 1",
"[SCOREBOARD] 2": "Jugador 2",

View File

@@ -138,6 +138,15 @@ auto Input::gameControllerFound() const -> bool { return !gamepads_.empty(); }
// Obten el nombre de un mando de juego
auto Input::getControllerName(std::shared_ptr<Gamepad> gamepad) const -> std::string { return gamepad == nullptr ? std::string() : gamepad->name; }
// Obtiene la lista de nombres de mandos
auto Input::getControllerNames() const -> std::vector<std::string> {
std::vector<std::string> names;
for (const auto &gamepad : gamepads_) {
names.push_back(gamepad->name);
}
return names;
}
// Obten el número de mandos conectados
auto Input::getNumGamepads() const -> int { return gamepads_.size(); }
@@ -151,6 +160,15 @@ std::shared_ptr<Input::Gamepad> Input::getGamepad(SDL_JoystickID id) const {
return nullptr;
}
std::shared_ptr<Input::Gamepad> Input::getGamepadByName(const std::string &name) const {
for (const auto &gamepad : gamepads_) {
if (gamepad && gamepad->name == name) {
return gamepad;
}
}
return nullptr;
}
// Obtiene el SDL_GamepadButton asignado a un input
auto Input::getControllerBinding(std::shared_ptr<Gamepad> gamepad, Action input) const -> SDL_GamepadButton {
return gamepad->bindings[input].button;

View File

@@ -153,8 +153,10 @@ class Input {
// --- Métodos de gestión de mandos ---
[[nodiscard]] auto gameControllerFound() const -> bool;
auto getControllerName(std::shared_ptr<Gamepad> gamepad) const -> std::string;
auto getControllerNames() const -> std::vector<std::string>;
[[nodiscard]] auto getNumGamepads() const -> int;
std::shared_ptr<Gamepad> getGamepad(SDL_JoystickID id) const;
std::shared_ptr<Input::Gamepad> getGamepadByName(const std::string &name) const;
const Gamepads &getGamepads() const { return gamepads_; }
// --- Métodos de consulta y utilidades ---
@@ -170,7 +172,6 @@ class Input {
void printConnectedGamepads() const;
//[[nodiscard]] auto getGamepads() const -> const Gamepads & { return gamepads_; }
std::shared_ptr<Gamepad> findAvailableGamepadByName(const std::string &gamepad_name);
void saveGamepadConfigFromGamepad(std::shared_ptr<Gamepad> gamepad);

View File

@@ -1,6 +1,6 @@
#include "menu_renderer.h"
#include <algorithm> // Para max
#include <algorithm> // Para max, min
#include <utility> // Para pair, move
#include "color.h" // Para Color, generateMirroredCycle, ColorCycleStyle
@@ -8,10 +8,12 @@
#include "param.h" // Para Param, param, ParamServiceMenu, ParamGame
#include "screen.h" // Para Screen
#include "text.h" // Para Text, TEXT_CENTER, TEXT_COLOR
#include "utils.h" // Para Zone
#include "utils.h" // Para Zone, truncateWithEllipsis
MenuRenderer::MenuRenderer(const ServiceMenu *menu_state, std::shared_ptr<Text> element_text, std::shared_ptr<Text> title_text)
: element_text_(std::move(element_text)), title_text_(std::move(title_text)) {}
: element_text_(std::move(element_text)), title_text_(std::move(title_text)) {
initializeMaxSizes();
}
void MenuRenderer::render(const ServiceMenu *menu_state) {
// Dibuja la sombra
@@ -48,11 +50,22 @@ void MenuRenderer::render(const ServiceMenu *menu_state) {
const Color &current_color = IS_SELECTED ? param.service_menu.selected_color : param.service_menu.text_color;
if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
// Para opciones alineadas a la izquierda, truncamos el valor si es necesario
const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) -
element_text_->lenght(option_pairs.at(i).first, -2) -
ServiceMenu::MIN_GAP_OPTION_VALUE;
const std::string truncated_value = getTruncatedValue(option_pairs.at(i).second, available_width);
element_text_->writeColored(rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, option_pairs.at(i).first, current_color, -2);
const int X = rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING - element_text_->lenght(option_pairs.at(i).second, -2);
element_text_->writeColored(X, y, option_pairs.at(i).second, current_color, -2);
const int X = rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING - element_text_->lenght(truncated_value, -2);
element_text_->writeColored(X, y, truncated_value, current_color, -2);
} else {
element_text_->writeDX(TEXT_CENTER | TEXT_COLOR, rect_.x + rect_.w / 2, y, option_pairs.at(i).first, -2, current_color);
// Para opciones centradas, también truncamos si es necesario
const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
const std::string truncated_caption = getTruncatedValue(option_pairs.at(i).first, available_width);
element_text_->writeDX(TEXT_CENTER | TEXT_COLOR, rect_.x + rect_.w / 2, y, truncated_caption, -2, current_color);
}
y += options_height_ + options_padding_;
}
@@ -80,6 +93,13 @@ void MenuRenderer::setLayout(const ServiceMenu *menu_state) {
setSize(menu_state);
}
void MenuRenderer::initializeMaxSizes() {
// Establecemos los límites máximos basados en el tamaño de la pantalla
// Dejamos un margen del 10% en cada lado para que el menú no ocupe toda la pantalla
max_menu_width_ = static_cast<size_t>(param.game.game_area.rect.w * 0.9f);
max_menu_height_ = static_cast<size_t>(param.game.game_area.rect.h * 0.9f);
}
void MenuRenderer::setAnchors(const ServiceMenu *menu_state) {
size_t max_entries = 0;
for (int i = 0; i < 5; ++i) {
@@ -102,10 +122,11 @@ void MenuRenderer::setAnchors(const ServiceMenu *menu_state) {
}
auto MenuRenderer::calculateNewRect(const ServiceMenu *menu_state) -> SDL_FRect {
width_ = getMenuWidthForGroup(menu_state->getCurrentGroup());
width_ = std::min(static_cast<size_t>(getMenuWidthForGroup(menu_state->getCurrentGroup())), max_menu_width_);
const auto &display_options = menu_state->getDisplayOptions();
lower_height_ = ((!display_options.empty() ? display_options.size() - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2);
height_ = upper_height_ + lower_height_;
height_ = std::min(upper_height_ + lower_height_, max_menu_height_);
return {(param.game.width - width_) / 2.0F, (param.game.height - height_) / 2.0F, (float)width_, (float)height_};
}
@@ -165,14 +186,21 @@ void MenuRenderer::precalculateMenuWidths(const std::vector<std::unique_ptr<Menu
}
max_option_width = std::max(max_option_width, element_text_->lenght(option->getCaption(), -2));
if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
max_value_width = std::max(max_value_width, option->getMaxValueWidth(element_text_.get()));
// Para calcular el ancho máximo, necesitamos considerar la truncación
int max_available_value_width = static_cast<int>(max_menu_width_) - max_option_width -
(ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) -
ServiceMenu::MIN_GAP_OPTION_VALUE;
int actual_value_width = getTruncatedValueWidth(option->getValueAsString(), max_available_value_width);
max_value_width = std::max(max_value_width, actual_value_width);
}
}
size_t total_width = max_option_width + (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
total_width += ServiceMenu::MIN_GAP_OPTION_VALUE + max_value_width;
}
group_menu_widths_[group] = std::max((int)ServiceMenu::MIN_WIDTH, (int)total_width);
group_menu_widths_[group] = std::min(std::max((int)ServiceMenu::MIN_WIDTH, (int)total_width),
static_cast<int>(max_menu_width_));
}
}
@@ -197,4 +225,56 @@ auto MenuRenderer::getAnimatedSelectedColor() const -> Color {
auto MenuRenderer::setRect(SDL_FRect rect) -> SDL_FRect {
border_rect_ = {rect.x - 1, rect.y + 1, rect.w + 2, rect.h - 2};
return rect;
}
auto MenuRenderer::getTruncatedValueWidth(const std::string &value, int available_width) const -> int {
int value_width = element_text_->lenght(value, -2);
if (value_width <= available_width) {
return value_width;
}
// Calculamos cuántos caracteres podemos mostrar más los puntos suspensivos
// Estimamos el ancho de los puntos suspensivos como 3 caracteres promedio
int ellipsis_width = element_text_->lenght("...", -2);
int available_for_text = available_width - ellipsis_width;
if (available_for_text <= 0) {
return ellipsis_width; // Solo mostramos los puntos suspensivos
}
// Calculamos aproximadamente cuántos caracteres caben
float char_width = static_cast<float>(value_width) / value.length();
size_t max_chars = static_cast<size_t>(available_for_text / char_width);
// Verificamos el ancho real del texto truncado
std::string truncated = truncateWithEllipsis(value, max_chars);
return element_text_->lenght(truncated, -2);
}
auto MenuRenderer::getTruncatedValue(const std::string &value, int available_width) const -> std::string {
int value_width = element_text_->lenght(value, -2);
if (value_width <= available_width) {
return value;
}
// Calculamos cuántos caracteres podemos mostrar
int ellipsis_width = element_text_->lenght("...", -2);
int available_for_text = available_width - ellipsis_width;
if (available_for_text <= 0) {
return "..."; // Solo mostramos los puntos suspensivos
}
// Calculamos aproximadamente cuántos caracteres caben
float char_width = static_cast<float>(value_width) / value.length();
size_t max_chars = static_cast<size_t>(available_for_text / char_width);
// Ajustamos iterativamente hasta que el texto quepa
std::string truncated = truncateWithEllipsis(value, max_chars);
while (element_text_->lenght(truncated, -2) > available_width && max_chars > 1) {
max_chars--;
truncated = truncateWithEllipsis(value, max_chars);
}
return truncated;
}

View File

@@ -49,6 +49,10 @@ class MenuRenderer {
size_t lower_padding_ = 0;
Uint32 color_counter_ = 0;
// --- Límites de tamaño máximo ---
size_t max_menu_width_ = 0;
size_t max_menu_height_ = 0;
// --- Variables para animación de resize ---
SDL_FRect rect_anim_from_{};
SDL_FRect rect_anim_to_{};
@@ -57,9 +61,10 @@ class MenuRenderer {
bool resizing_ = false;
// --- Anchos precalculados ---
std::array<int, 5> group_menu_widths_ = {};
std::array<int, ServiceMenu::SETTINGS_GROUP_SIZE> group_menu_widths_ = {};
// --- Métodos privados de la vista ---
void initializeMaxSizes();
void setAnchors(const ServiceMenu *menu_state);
auto calculateNewRect(const ServiceMenu *menu_state) -> SDL_FRect;
void resize(const ServiceMenu *menu_state);
@@ -70,4 +75,6 @@ class MenuRenderer {
[[nodiscard]] auto getAnimatedSelectedColor() const -> Color;
void updateColorCounter();
auto setRect(SDL_FRect rect) -> SDL_FRect;
};
[[nodiscard]] auto getTruncatedValueWidth(const std::string &value, int available_width) const -> int;
[[nodiscard]] auto getTruncatedValue(const std::string &value, int available_width) const -> std::string;
};

View File

@@ -176,6 +176,9 @@ void ServiceMenu::updateMenu() {
}
void ServiceMenu::applySettings() {
if (current_settings_group_ == SettingsGroup::CONTROLS) {
applyControlsSettings();
}
if (current_settings_group_ == SettingsGroup::VIDEO) {
applyVideoSettings();
}
@@ -190,6 +193,8 @@ void ServiceMenu::applySettings() {
updateOptionPairs();
}
void ServiceMenu::applyControlsSettings() {}
void ServiceMenu::applyVideoSettings() {
Screen::get()->applySettings();
setHiddenOptions();
@@ -216,6 +221,7 @@ auto ServiceMenu::getOptionByCaption(const std::string &caption) const -> MenuOp
auto ServiceMenu::getCurrentGroupAlignment() const -> ServiceMenu::GroupAlignment {
switch (current_settings_group_) {
case SettingsGroup::CONTROLS:
case SettingsGroup::VIDEO:
case SettingsGroup::AUDIO:
case SettingsGroup::SETTINGS:
@@ -239,6 +245,43 @@ auto ServiceMenu::countOptionsInGroup(SettingsGroup group) const -> size_t {
void ServiceMenu::initializeOptions() {
options_.clear();
// CONTROLS
options_.push_back(std::make_unique<ListOption>(
Lang::getText("[SERVICE_MENU] CONTROLLER1"),
SettingsGroup::CONTROLS,
Input::get()->getControllerNames(),
[]() {
return Options::gamepad_manager.getGamepad(Player::Id::PLAYER1).name;
},
[](const std::string &val) {
Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER1, Input::get()->getGamepadByName(val), val);
}));
options_.push_back(std::make_unique<ListOption>(
Lang::getText("[SERVICE_MENU] CONTROLLER2"),
SettingsGroup::CONTROLS,
Input::get()->getControllerNames(),
[]() {
return Options::gamepad_manager.getGamepad(Player::Id::PLAYER2).name;
},
[](const std::string &val) {
Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER2, Input::get()->getGamepadByName(val), val);
}));
options_.push_back(std::make_unique<ListOption>(
Lang::getText("[SERVICE_MENU] KEYBOARD"),
SettingsGroup::CONTROLS,
std::vector<std::string>{
Lang::getText("[SERVICE_MENU] PLAYER1"),
Lang::getText("[SERVICE_MENU] PLAYER2")},
[]() {
return Lang::getNameFromCode(Options::pending_changes.new_language);
},
[](const std::string &val) {
Options::pending_changes.new_language = Lang::getCodeFromName(val);
Options::checkPendingChanges();
}));
// VIDEO
options_.push_back(std::make_unique<BoolOption>(
Lang::getText("[SERVICE_MENU] FULLSCREEN"),
@@ -366,6 +409,11 @@ void ServiceMenu::initializeOptions() {
!Options::settings.shutdown_enabled));
// MAIN MENU
options_.push_back(std::make_unique<FolderOption>(
Lang::getText("[SERVICE_MENU] CONTROLS"),
SettingsGroup::MAIN,
SettingsGroup::CONTROLS));
options_.push_back(std::make_unique<FolderOption>(
Lang::getText("[SERVICE_MENU] VIDEO"),
SettingsGroup::MAIN,
@@ -410,6 +458,8 @@ auto ServiceMenu::settingsGroupToString(SettingsGroup group) -> std::string {
switch (group) {
case SettingsGroup::MAIN:
return Lang::getText("[SERVICE_MENU] TITLE");
case SettingsGroup::CONTROLS:
return Lang::getText("[SERVICE_MENU] CONTROLS");
case SettingsGroup::VIDEO:
return Lang::getText("[SERVICE_MENU] VIDEO");
case SettingsGroup::AUDIO:

View File

@@ -9,11 +9,12 @@
#include "ui_message.h" // Para UIMessage
class MenuOption;
class MenuRenderer; // <-- Nuevo
class MenuRenderer;
class ServiceMenu {
public:
enum class SettingsGroup {
CONTROLS,
VIDEO,
AUDIO,
SETTINGS,
@@ -30,6 +31,7 @@ class ServiceMenu {
static constexpr size_t OPTIONS_HORIZONTAL_PADDING = 20;
static constexpr size_t MIN_WIDTH = 240;
static constexpr size_t MIN_GAP_OPTION_VALUE = 30;
static constexpr size_t SETTINGS_GROUP_SIZE = 6;
// --- Métodos de singleton ---
static void init();
@@ -88,6 +90,7 @@ class ServiceMenu {
void initializeOptions();
void updateMenu();
void applySettings();
void applyControlsSettings();
void applyVideoSettings();
static void applyAudioSettings();
void applySettingsSettings();

View File

@@ -383,4 +383,16 @@ auto getFileName(const std::string &path) -> std::string {
auto getPath(const std::string &full_path) -> std::string {
std::filesystem::path path(full_path);
return path.parent_path().string();
}
// Trunca un string y le añade puntos suspensivos
auto truncateWithEllipsis(const std::string &input, size_t length) -> std::string {
if (input.size() <= length) {
return input;
}
if (length <= 3) {
// Not enough space for any content plus ellipsis
return std::string(length, '.');
}
return input.substr(0, length) + "...";
}

View File

@@ -116,6 +116,7 @@ auto easeInCubic(double time) -> double;
// Utilidades varias
auto stringInVector(const std::vector<std::string> &vec, const std::string &str) -> bool; // Comprueba si un vector contiene una cadena
void printWithDots(const std::string &text1, const std::string &text2, const std::string &text3); // Imprime una línea con puntos
auto truncateWithEllipsis(const std::string &input, size_t length) -> std::string; // Trunca un string y le añade puntos suspensivos
// Demo
auto loadDemoDataFromFile(const std::string &file_path) -> DemoData;