claude: treballant en el nou define_buttons

This commit is contained in:
2025-08-06 14:12:29 +02:00
parent 1224af2a9b
commit 6d36291f51
12 changed files with 650 additions and 190 deletions

View File

@@ -1,69 +1,55 @@
#include "define_buttons.h" #include "define_buttons.h"
#include <algorithm> // Para __all_of_fn, all_of #include <algorithm>
#include <functional> // Para identity #include <functional>
#include <memory> // Para allocator, shared_ptr, __shared_ptr_access, operator== #include <memory>
#include "input.h" // Para Input #include "input.h"
#include "input_types.h" // Para InputAction #include "input_types.h"
#include "lang.h" // Para getText #include "lang.h"
#include "options.h" // Para Gamepad #include "options.h"
#include "param.h" // Para Param, param, ParamGame, ParamTitle #include "param.h"
#include "resource.h" // Para Resource #include "resource.h"
#include "text.h" // Para Text #include "text.h"
#include "ui/window_message.h"
// Constructor
DefineButtons::DefineButtons() DefineButtons::DefineButtons()
: input_(Input::get()), : input_(Input::get()) {
x_(param.game.width / 2),
y_(param.title.press_start_position) {
clearButtons(); clearButtons();
auto gamepads = input_->getGamepads(); auto gamepads = input_->getGamepads();
for (auto gamepad : gamepads) { for (auto gamepad : gamepads) {
controller_names_.emplace_back(Input::getControllerName(gamepad)); controller_names_.emplace_back(Input::getControllerName(gamepad));
} }
// Crear la ventana de mensaje
auto text_renderer = Resource::get()->getText("04b_25_flat");
window_message_ = std::make_unique<WindowMessage>(
text_renderer,
Lang::getText("[DEFINE_BUTTONS] TITLE"),
Color{20, 30, 50, 200}, // Fondo azul oscuro semi-transparente
Color{100, 150, 200, 255}, // Borde azul claro
Color{255, 255, 255, 255}, // Título blanco
Color{220, 220, 220, 255} // Texto gris claro
);
window_message_->setPadding(20.0f);
window_message_->setLineSpacing(8.0f);
} }
// Dibuja el objeto en pantalla
void DefineButtons::render() { void DefineButtons::render() {
static auto text_ = Resource::get()->getText("8bithud"); if (enabled_ && window_message_) {
if (enabled_) { window_message_->render();
text_->writeCentered(x_, y_ - 10, Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(static_cast<int>(options_gamepad_->player_id)));
text_->writeCentered(x_, y_, options_gamepad_->name);
text_->writeCentered(x_, y_ + 10, buttons_.at(index_button_).label);
} }
} }
// Comprueba el botón que se ha pulsado void DefineButtons::update() {
void DefineButtons::doControllerButtonDown(const SDL_GamepadButtonEvent &event) { if (enabled_ && window_message_) {
auto gamepad = input_->getGamepad(event.which); window_message_->update();
// Asegúrate de que el gamepad sea válido y sea el que corresponde
if (!gamepad || gamepad != options_gamepad_->instance) {
return;
}
const auto BUTTON = static_cast<SDL_GamepadButton>(event.button);
if (checkButtonNotInUse(BUTTON)) {
buttons_.at(index_button_).button = BUTTON;
incIndexButton();
} }
} }
// Asigna los botones definidos al input_
void DefineButtons::bindButtons(Options::Gamepad *options_gamepad) {
for (const auto &button : buttons_) {
Input::bindGameControllerButton(options_gamepad->instance, button.action, button.button);
}
// Remapea los inputs a inputs
Input::bindGameControllerButton(options_gamepad->instance, Input::Action::SM_SELECT, Input::Action::FIRE_LEFT);
Input::bindGameControllerButton(options_gamepad->instance, Input::Action::SM_BACK, Input::Action::FIRE_CENTER);
}
// Comprueba los eventos
void DefineButtons::checkEvents(const SDL_Event &event) { void DefineButtons::checkEvents(const SDL_Event &event) {
if (enabled_) { if (enabled_) {
switch (event.type) { switch (event.type) {
@@ -79,7 +65,6 @@ void DefineButtons::checkEvents(const SDL_Event &event) {
} }
} }
// Habilita el objeto
auto DefineButtons::enable(Options::Gamepad *options_gamepad) -> bool { auto DefineButtons::enable(Options::Gamepad *options_gamepad) -> bool {
if (options_gamepad != nullptr) { if (options_gamepad != nullptr) {
options_gamepad_ = options_gamepad; options_gamepad_ = options_gamepad;
@@ -87,12 +72,52 @@ auto DefineButtons::enable(Options::Gamepad *options_gamepad) -> bool {
finished_ = false; finished_ = false;
index_button_ = 0; index_button_ = 0;
clearButtons(); clearButtons();
updateWindowMessage();
if (window_message_) {
window_message_->autoSize();
window_message_->centerOnScreen();
window_message_->show();
}
return true; return true;
} }
return false; return false;
} }
// Incrementa el indice de los botones void DefineButtons::disable() {
enabled_ = false;
finished_ = false;
if (window_message_) {
window_message_->hide();
}
}
void DefineButtons::doControllerButtonDown(const SDL_GamepadButtonEvent &event) {
auto gamepad = input_->getGamepad(event.which);
if (!gamepad || gamepad != options_gamepad_->instance) {
return;
}
const auto BUTTON = static_cast<SDL_GamepadButton>(event.button);
if (checkButtonNotInUse(BUTTON)) {
buttons_.at(index_button_).button = BUTTON;
incIndexButton();
updateWindowMessage();
}
}
void DefineButtons::bindButtons(Options::Gamepad *options_gamepad) {
for (const auto &button : buttons_) {
Input::bindGameControllerButton(options_gamepad->instance, button.action, button.button);
}
Input::bindGameControllerButton(options_gamepad->instance, Input::Action::SM_SELECT, Input::Action::FIRE_LEFT);
Input::bindGameControllerButton(options_gamepad->instance, Input::Action::SM_BACK, Input::Action::FIRE_CENTER);
}
void DefineButtons::incIndexButton() { void DefineButtons::incIndexButton() {
if (index_button_ < buttons_.size() - 1) { if (index_button_ < buttons_.size() - 1) {
++index_button_; ++index_button_;
@@ -101,14 +126,12 @@ void DefineButtons::incIndexButton() {
} }
} }
// Comprueba que un botón no esté ya asignado
auto DefineButtons::checkButtonNotInUse(SDL_GamepadButton button) -> bool { auto DefineButtons::checkButtonNotInUse(SDL_GamepadButton button) -> bool {
return std::ranges::all_of(buttons_, [button](const auto &b) { return std::ranges::all_of(buttons_, [button](const auto &b) {
return b.button != button; return b.button != button;
}); });
} }
// Limpia la asignación de botones
void DefineButtons::clearButtons() { void DefineButtons::clearButtons() {
buttons_.clear(); buttons_.clear();
buttons_.emplace_back(Lang::getText("[DEFINE_BUTTONS] FIRE_LEFT"), Input::Action::FIRE_LEFT, SDL_GAMEPAD_BUTTON_INVALID); buttons_.emplace_back(Lang::getText("[DEFINE_BUTTONS] FIRE_LEFT"), Input::Action::FIRE_LEFT, SDL_GAMEPAD_BUTTON_INVALID);
@@ -118,12 +141,57 @@ void DefineButtons::clearButtons() {
buttons_.emplace_back(Lang::getText("[DEFINE_BUTTONS] SERVICE_MENU"), Input::Action::SERVICE, SDL_GAMEPAD_BUTTON_INVALID); buttons_.emplace_back(Lang::getText("[DEFINE_BUTTONS] SERVICE_MENU"), Input::Action::SERVICE, SDL_GAMEPAD_BUTTON_INVALID);
} }
// Comprueba si ha finalizado
void DefineButtons::checkEnd() { void DefineButtons::checkEnd() {
if (finished_) { if (finished_) {
bindButtons(options_gamepad_); // Asigna los botones definidos al input_ bindButtons(options_gamepad_);
input_->saveGamepadConfigFromGamepad(options_gamepad_->instance); // Guarda los cambios input_->saveGamepadConfigFromGamepad(options_gamepad_->instance);
input_->resetInputStates(); // Reinicia los estados de las pulsaciones de los botones input_->resetInputStates();
enabled_ = false; // Deshabilita
// Mostrar mensaje de finalización brevemente
if (window_message_) {
window_message_->clearTexts();
window_message_->addText(Lang::getText("[DEFINE_BUTTONS] CONFIGURATION_COMPLETE"));
window_message_->autoSize();
window_message_->centerOnScreen();
}
// Se deshabilitará desde el ServiceMenu después de un breve delay
}
}
void DefineButtons::updateWindowMessage() {
if (!window_message_ || !options_gamepad_) {
return;
}
// Configurar título
std::string title = Lang::getText("[DEFINE_BUTTONS] CONFIGURING") + ": " + options_gamepad_->name;
window_message_->setTitle(title);
// Limpiar textos anteriores
window_message_->clearTexts();
if (index_button_ < buttons_.size()) {
// Mostrar progreso
std::string progress = "(" + std::to_string(index_button_ + 1) + "/" +
std::to_string(buttons_.size()) + ")";
window_message_->addText(progress);
window_message_->addText("");
// Instrucción actual
std::string instruction = Lang::getText("[DEFINE_BUTTONS] PRESS_BUTTON_FOR") + ":";
window_message_->addText(instruction);
window_message_->addText(buttons_.at(index_button_).label);
// Botones ya configurados
if (index_button_ > 0) {
window_message_->addText("");
window_message_->addText(Lang::getText("[DEFINE_BUTTONS] CONFIGURED") + ":");
for (size_t i = 0; i < index_button_; ++i) {
std::string configured = "" + buttons_[i].label;
window_message_->addText(configured);
}
}
} }
} }

View File

@@ -1,58 +1,59 @@
#pragma once #pragma once
#include <SDL3/SDL.h> // Para SDL_GamepadButton, SDL_Event, SDL_GamepadButtonEvent #include <SDL3/SDL.h>
#include <cstddef> // Para size_t #include <cstddef>
#include <string> // Para string #include <memory>
#include <utility> // Para move #include <string>
#include <vector> // Para vector #include <utility>
#include <vector>
#include "input.h" // Para Input #include "input.h"
class WindowMessage;
namespace Options { namespace Options {
struct Gamepad; struct Gamepad;
} // namespace Options }
// Clase DefineButtons
class DefineButtons { class DefineButtons {
public: public:
// Estructura para definir botones
struct Button { struct Button {
std::string label; // Texto en pantalla std::string label;
Input::Action action; // Acción asociada Input::Action action;
SDL_GamepadButton button; // Botón del mando SDL_GamepadButton button;
Button(std::string label, Input::Action action, SDL_GamepadButton button) Button(std::string label, Input::Action action, SDL_GamepadButton button)
: label(std::move(label)), action(action), button(button) {} : label(std::move(label)), action(action), button(button) {}
}; };
DefineButtons(); DefineButtons();
~DefineButtons() = default; ~DefineButtons() = default;
void render(); // Dibuja el objeto en pantalla void render();
void checkEvents(const SDL_Event &event); // Procesa los eventos void update();
auto enable(Options::Gamepad *options_gamepad) -> bool; // Habilita la redefinición de botones void checkEvents(const SDL_Event &event);
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }; // Comprueba si está habilitado auto enable(Options::Gamepad *options_gamepad) -> bool;
void disable();
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto isFinished() const -> bool { return finished_; }
private: private:
// Objetos Input *input_ = nullptr;
Input *input_ = nullptr; // Gestión de entrada std::unique_ptr<WindowMessage> window_message_;
// Variables bool enabled_ = false;
bool enabled_ = false; // Indica si está activo bool finished_ = false;
bool finished_ = false; // Indica si ha terminado std::vector<Button> buttons_;
int x_ = 0; // Coordenadas de texto size_t index_button_ = 0;
int y_ = 0; // Coordenadas de texto std::vector<std::string> controller_names_;
std::vector<Button> buttons_; // Definiciones de botones Options::Gamepad *options_gamepad_ = nullptr;
size_t index_button_ = 0; // Índice del botón en proceso
std::vector<std::string> controller_names_; // Nombres de los mandos
Options::Gamepad *options_gamepad_;
// Métodos internos void incIndexButton();
void incIndexButton(); // Incrementa el índice de botones void doControllerButtonDown(const SDL_GamepadButtonEvent &event);
void doControllerButtonDown(const SDL_GamepadButtonEvent &event); // Procesa pulsaciones void bindButtons(Options::Gamepad *options_gamepad);
void bindButtons(Options::Gamepad *options_gamepad); // Asigna botones al sistema de entrada auto checkButtonNotInUse(SDL_GamepadButton button) -> bool;
auto checkButtonNotInUse(SDL_GamepadButton button) -> bool; // Verifica uso de botones void clearButtons();
void clearButtons(); // Limpia asignaciones actuales void checkEnd();
void checkEnd(); // Comprueba si ha finalizado void updateWindowMessage();
}; };

View File

@@ -43,7 +43,6 @@ Title::Title()
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)), tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)),
game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)), game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)),
mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))), mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))),
define_buttons_(std::make_unique<DefineButtons>()),
num_controllers_(Input::get()->getNumGamepads()), num_controllers_(Input::get()->getNumGamepads()),
state_(TitleState::LOGO_ANIMATING) { state_(TitleState::LOGO_ANIMATING) {
// Configura objetos // Configura objetos
@@ -102,7 +101,6 @@ void Title::render() {
renderPlayers(); renderPlayers();
renderStartPrompt(); renderStartPrompt();
renderCopyright(); renderCopyright();
define_buttons_->render();
fade_->render(); fade_->render();
SCREEN->render(); SCREEN->render();
@@ -117,15 +115,10 @@ void Title::checkEvents() {
} }
GlobalEvents::check(event); GlobalEvents::check(event);
define_buttons_->checkEvents(event);
} }
} }
void Title::handleKeyDownEvent(const SDL_Event& event) { void Title::handleKeyDownEvent(const SDL_Event& event) {
bool is_first_press = static_cast<int>(event.key.repeat) == 0;
if (is_first_press) {
handleControlKeys(event.key.key);
}
#ifdef _DEBUG #ifdef _DEBUG
bool is_repeat = static_cast<int>(event.key.repeat) == 1; bool is_repeat = static_cast<int>(event.key.repeat) == 1;
if (is_repeat) { if (is_repeat) {
@@ -209,44 +202,8 @@ void Title::printColorValue(const Color& color) {
} }
#endif #endif
void Title::handleControlKeys(SDL_Keycode key) {
switch (key) {
case SDLK_1:
define_buttons_->enable(&Options::gamepad_manager.getGamepad(Player::Id::PLAYER1));
resetCounter();
break;
case SDLK_2:
define_buttons_->enable(&Options::gamepad_manager.getGamepad(Player::Id::PLAYER2));
resetCounter();
break;
case SDLK_3:
swapControllers();
resetCounter();
break;
case SDLK_4:
swapKeyboard();
resetCounter();
break;
case SDLK_5:
showControllers();
resetCounter();
break;
default:
break;
}
}
// Comprueba las entradas // Comprueba las entradas
void Title::checkInput() { void Title::checkInput() {
if (shouldSkipInputCheck()) {
return;
}
Input::get()->update(); Input::get()->update();
if (!ServiceMenu::get()->isEnabled()) { if (!ServiceMenu::get()->isEnabled()) {
@@ -256,10 +213,6 @@ void Title::checkInput() {
GlobalInputs::check(); GlobalInputs::check();
} }
auto Title::shouldSkipInputCheck() const -> bool {
return define_buttons_->isEnabled();
}
void Title::processControllerInputs() { void Title::processControllerInputs() {
for (const auto& controller : Options::gamepad_manager) { for (const auto& controller : Options::gamepad_manager) {
if (isStartButtonPressed(&controller)) { if (isStartButtonPressed(&controller)) {
@@ -396,14 +349,9 @@ void Title::updateState() {
break; break;
} }
case TitleState::LOGO_FINISHED: { case TitleState::LOGO_FINISHED: {
// El contador solo sube si no estamos definiendo botones ++counter_; // Incrementa el contador
counter_ = define_buttons_->isEnabled() ? 0 : counter_ + 1; game_logo_->update(); // Actualiza el logo con el título del juego
tiled_bg_->update(); // Actualiza el mosaico de fondo
// Actualiza el logo con el título del juego
game_logo_->update();
// Actualiza el mosaico de fondo
tiled_bg_->update();
if (counter_ == param.title.title_duration) { if (counter_ == param.title.title_duration) {
// El menu ha hecho time out // El menu ha hecho time out
@@ -411,20 +359,16 @@ void Title::updateState() {
fade_->activate(); fade_->activate();
selection_ = Section::Options::TITLE_TIME_OUT; selection_ = Section::Options::TITLE_TIME_OUT;
} }
break; break;
} }
case TitleState::START_HAS_BEEN_PRESSED: { case TitleState::START_HAS_BEEN_PRESSED: {
// Actualiza el logo con el título del juego ++counter_; // Incrementa el contador
game_logo_->update(); game_logo_->update(); // Actualiza el logo con el título del juego
tiled_bg_->update(); // Actualiza el mosaico de fondo
// Actualiza el mosaico de fondo
tiled_bg_->update();
if (counter_ == 100) { if (counter_ == 100) {
fade_->activate(); fade_->activate();
} }
++counter_;
break; break;
} }
@@ -443,19 +387,17 @@ void Title::updateStartPrompt() {
Uint32 time_ms = SDL_GetTicks(); Uint32 time_ms = SDL_GetTicks();
bool condition_met = false; bool condition_met = false;
if (!define_buttons_->isEnabled()) { switch (state_) {
switch (state_) { case TitleState::LOGO_FINISHED:
case TitleState::LOGO_FINISHED: condition_met = (time_ms % LOGO_BLINK_PERIOD) >= (LOGO_BLINK_PERIOD - LOGO_BLINK_ON_TIME);
condition_met = (time_ms % LOGO_BLINK_PERIOD) >= (LOGO_BLINK_PERIOD - LOGO_BLINK_ON_TIME); break;
break;
case TitleState::START_HAS_BEEN_PRESSED: case TitleState::START_HAS_BEEN_PRESSED:
condition_met = (time_ms % START_BLINK_PERIOD) >= (START_BLINK_PERIOD - START_BLINK_ON_TIME); condition_met = (time_ms % START_BLINK_PERIOD) >= (START_BLINK_PERIOD - START_BLINK_ON_TIME);
break; break;
default: default:
break; break;
}
} }
should_render_start_prompt_ = condition_met; should_render_start_prompt_ = condition_met;

View File

@@ -10,7 +10,6 @@
#include "player.h" // Para Player #include "player.h" // Para Player
#include "section.hpp" // Para Options, Name (ptr only) #include "section.hpp" // Para Options, Name (ptr only)
class DefineButtons;
class Fade; class Fade;
class GameLogo; class GameLogo;
class Sprite; class Sprite;
@@ -63,7 +62,6 @@ class Title {
std::unique_ptr<TiledBG> tiled_bg_; // Fondo animado de tiles std::unique_ptr<TiledBG> tiled_bg_; // Fondo animado de tiles
std::unique_ptr<GameLogo> game_logo_; // Logo del juego std::unique_ptr<GameLogo> game_logo_; // Logo del juego
std::unique_ptr<Sprite> mini_logo_sprite_; // Logo JailGames mini std::unique_ptr<Sprite> mini_logo_sprite_; // Logo JailGames mini
std::unique_ptr<DefineButtons> define_buttons_; // Definición de botones del joystick
std::vector<std::shared_ptr<Player>> players_; // Vector de jugadores std::vector<std::shared_ptr<Player>> players_; // Vector de jugadores
// --- Variables de estado --- // --- Variables de estado ---
@@ -90,8 +88,6 @@ class Title {
void checkEvents(); // Comprueba los eventos void checkEvents(); // Comprueba los eventos
void checkInput(); // Comprueba las entradas void checkInput(); // Comprueba las entradas
void handleKeyDownEvent(const SDL_Event& event); // Maneja el evento de tecla presionada void handleKeyDownEvent(const SDL_Event& event); // Maneja el evento de tecla presionada
void handleControlKeys(SDL_Keycode key); // Maneja las teclas de control específicas
[[nodiscard]] auto shouldSkipInputCheck() const -> bool; // Determina si se debe omitir la comprobación de entrada
void processControllerInputs(); // Procesa las entradas de los mandos void processControllerInputs(); // Procesa las entradas de los mandos
[[nodiscard]] static auto isStartButtonPressed(const Options::Gamepad* controller) -> bool; // Comprueba si se ha pulsado el botón Start [[nodiscard]] static auto isStartButtonPressed(const Options::Gamepad* controller) -> bool; // Comprueba si se ha pulsado el botón Start
void handleStartButtonPress(const Options::Gamepad* controller); // Maneja la pulsación del botón Start void handleStartButtonPress(const Options::Gamepad* controller); // Maneja la pulsación del botón Start

View File

@@ -0,0 +1,95 @@
#include "action_list_option.h"
#include <algorithm>
#include "text.h"
ActionListOption::ActionListOption(
const std::string& caption,
ServiceMenu::SettingsGroup group,
std::vector<std::string> options,
ValueGetter getter,
ValueSetter setter,
ActionExecutor action_executor,
bool hidden) : MenuOption(caption, group, hidden),
options_(std::move(options)),
value_getter_(std::move(getter)),
value_setter_(std::move(setter)),
action_executor_(std::move(action_executor)),
current_index_(0) {
updateCurrentIndex();
}
auto ActionListOption::getBehavior() const -> Behavior {
// Puede tanto ajustar valor (lista) como ejecutar acción (botón)
return Behavior::BOTH;
}
auto ActionListOption::getValueAsString() const -> std::string {
if (value_getter_) {
return value_getter_();
}
if (current_index_ < options_.size()) {
return options_[current_index_];
}
return options_.empty() ? "" : options_[0];
}
auto ActionListOption::getMaxValueWidth(Text* text) const -> int {
int max_width = 0;
for (const auto& option : options_) {
int width = text->lenght(option, -2);
if (width > max_width) {
max_width = width;
}
}
return max_width;
}
void ActionListOption::adjustValue(bool up) {
if (options_.empty()) {
return;
}
if (up) {
current_index_ = (current_index_ + 1) % options_.size();
} else {
current_index_ = (current_index_ == 0) ? options_.size() - 1 : current_index_ - 1;
}
// Aplicar el cambio usando el setter
if (value_setter_ && current_index_ < options_.size()) {
value_setter_(options_[current_index_]);
}
}
void ActionListOption::executeAction() {
if (action_executor_) {
action_executor_();
}
}
void ActionListOption::sync() {
updateCurrentIndex();
}
void ActionListOption::updateCurrentIndex() {
current_index_ = findCurrentIndex();
}
auto ActionListOption::findCurrentIndex() const -> size_t {
if (!value_getter_ || options_.empty()) {
return 0;
}
const std::string current_value = value_getter_();
auto it = std::find(options_.begin(), options_.end(), current_value);
if (it != options_.end()) {
return static_cast<size_t>(std::distance(options_.begin(), it));
}
return 0; // Valor por defecto si no se encuentra
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include "menu_option.h"
#include <functional>
#include <string>
#include <vector>
class ActionListOption : public MenuOption {
public:
using ValueGetter = std::function<std::string()>;
using ValueSetter = std::function<void(const std::string&)>;
using ActionExecutor = std::function<void()>;
ActionListOption(
const std::string& caption,
ServiceMenu::SettingsGroup group,
std::vector<std::string> options,
ValueGetter getter,
ValueSetter setter,
ActionExecutor action_executor,
bool hidden = false
);
// Implementaciones de MenuOption
[[nodiscard]] auto getBehavior() const -> Behavior override;
[[nodiscard]] auto getValueAsString() const -> std::string override;
[[nodiscard]] auto getMaxValueWidth(Text* text) const -> int override;
void adjustValue(bool up) override;
void executeAction() override;
void sync(); //override;
private:
std::vector<std::string> options_;
ValueGetter value_getter_;
ValueSetter value_setter_;
ActionExecutor action_executor_;
size_t current_index_;
void updateCurrentIndex();
[[nodiscard]] auto findCurrentIndex() const -> size_t;
};

View File

@@ -17,8 +17,9 @@
class MenuOption { class MenuOption {
public: public:
enum class Behavior { enum class Behavior {
ADJUST, ADJUST, // Solo puede ajustar valor (como IntOption, BoolOption, ListOption)
SELECT SELECT, // Solo puede ejecutar acción (como ActionOption, FolderOption)
BOTH // Puede tanto ajustar como ejecutar acción (como ActionListOption)
}; };
MenuOption(std::string caption, ServiceMenu::SettingsGroup group, bool hidden = false) MenuOption(std::string caption, ServiceMenu::SettingsGroup group, bool hidden = false)

View File

@@ -45,27 +45,39 @@ void MenuRenderer::render(const ServiceMenu *menu_state) {
// Dibuja las opciones // Dibuja las opciones
y = options_y_; y = options_y_;
const auto &option_pairs = menu_state->getOptionPairs(); const auto &option_pairs = menu_state->getOptionPairs();
const auto &display_options = menu_state->getDisplayOptions();
for (size_t i = 0; i < option_pairs.size(); ++i) { for (size_t i = 0; i < option_pairs.size(); ++i) {
const bool IS_SELECTED = (i == menu_state->getSelectedIndex()); const bool IS_SELECTED = (i == menu_state->getSelectedIndex());
const Color &current_color = IS_SELECTED ? param.service_menu.selected_color : param.service_menu.text_color; const Color &current_color = IS_SELECTED ? param.service_menu.selected_color : param.service_menu.text_color;
if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) { if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
// Para opciones alineadas a la izquierda, truncamos el valor si es necesario // Para opciones alineadas a la izquierda, truncamos el valor si es necesario
const int AVAILABLE_WIDTH = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) - const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) -
element_text_->lenght(option_pairs.at(i).first, -2) - element_text_->lenght(option_pairs.at(i).first, -2) -
ServiceMenu::MIN_GAP_OPTION_VALUE; ServiceMenu::MIN_GAP_OPTION_VALUE;
const std::string TRUNCATED_VALUE = getTruncatedValue(option_pairs.at(i).second, AVAILABLE_WIDTH); std::string truncated_value = getTruncatedValue(option_pairs.at(i).second, available_width);
// Si la opción tiene Behavior::BOTH, añadir indicador visual
if (i < display_options.size() && display_options[i]->getBehavior() == MenuOption::Behavior::BOTH) {
truncated_value = "" + truncated_value + "";
}
element_text_->writeColored(rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, option_pairs.at(i).first, current_color, -2); 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(TRUNCATED_VALUE, -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); element_text_->writeColored(X, y, truncated_value, current_color, -2);
} else { } else {
// Para opciones centradas, también truncamos si es necesario // Para opciones centradas, también truncamos si es necesario
const int AVAILABLE_WIDTH = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2); const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
const std::string TRUNCATED_CAPTION = getTruncatedValue(option_pairs.at(i).first, AVAILABLE_WIDTH); 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); // Si la opción tiene Behavior::BOTH, añadir indicador visual
if (i < display_options.size() && display_options[i]->getBehavior() == MenuOption::Behavior::BOTH) {
truncated_caption = "" + truncated_caption + "";
}
element_text_->writeDX(TEXT_CENTER | TEXT_COLOR, rect_.x + rect_.w / 2, y, truncated_caption, -2, current_color);
} }
y += options_height_ + options_padding_; y += options_height_ + options_padding_;
} }

View File

@@ -16,6 +16,8 @@
#include "section.hpp" // Para Name, name, Options, options #include "section.hpp" // Para Name, name, Options, options
#include "ui/ui_message.h" // Para UIMessage #include "ui/ui_message.h" // Para UIMessage
#include "utils.h" // Para Zone #include "utils.h" // Para Zone
#include "action_list_option.h" // Para ActionListOption
#include "define_buttons.h" // Para DefineButtons
// Singleton // Singleton
ServiceMenu *ServiceMenu::instance = nullptr; ServiceMenu *ServiceMenu::instance = nullptr;
@@ -32,6 +34,7 @@ ServiceMenu::ServiceMenu()
renderer_ = std::make_unique<MenuRenderer>(this, element_text, title_text); renderer_ = std::make_unique<MenuRenderer>(this, element_text, title_text);
restart_message_ui_ = std::make_unique<UIMessage>(element_text, Lang::getText("[SERVICE_MENU] NEED_RESTART_MESSAGE"), param.service_menu.title_color); restart_message_ui_ = std::make_unique<UIMessage>(element_text, Lang::getText("[SERVICE_MENU] NEED_RESTART_MESSAGE"), param.service_menu.title_color);
define_buttons_ = std::make_unique<DefineButtons>();
reset(); reset();
} }
@@ -55,6 +58,11 @@ void ServiceMenu::render() {
const float MSG_Y = renderer_->getRect().y + 39.0F; const float MSG_Y = renderer_->getRect().y + 39.0F;
restart_message_ui_->setPosition(MSG_X, MSG_Y); restart_message_ui_->setPosition(MSG_X, MSG_Y);
restart_message_ui_->render(); restart_message_ui_->render();
// Renderizar DefineButtons si está activo (se dibuja por encima de todo)
if (define_buttons_ && define_buttons_->isEnabled()) {
define_buttons_->render();
}
} }
void ServiceMenu::update() { void ServiceMenu::update() {
@@ -70,6 +78,22 @@ void ServiceMenu::update() {
last_pending_changes_ = now_pending; last_pending_changes_ = now_pending;
} }
restart_message_ui_->update(); restart_message_ui_->update();
// Actualizar DefineButtons
if (define_buttons_) {
define_buttons_->update();
// Si DefineButtons ha terminado, deshabilitarlo
if (define_buttons_->isEnabled() && define_buttons_->isFinished()) {
// Pequeño delay antes de cerrar
static int finish_delay = 0;
finish_delay++;
if (finish_delay > 60) { // 1 segundo a 60 FPS
define_buttons_->disable();
finish_delay = 0;
}
}
}
} }
void ServiceMenu::reset() { void ServiceMenu::reset() {
@@ -247,8 +271,8 @@ auto ServiceMenu::countOptionsInGroup(SettingsGroup group) const -> size_t {
void ServiceMenu::initializeOptions() { void ServiceMenu::initializeOptions() {
options_.clear(); options_.clear();
// CONTROLS // CONTROLS - Usando ActionListOption para mandos
options_.push_back(std::make_unique<ListOption>( options_.push_back(std::make_unique<ActionListOption>(
Lang::getText("[SERVICE_MENU] CONTROLLER1"), Lang::getText("[SERVICE_MENU] CONTROLLER1"),
SettingsGroup::CONTROLS, SettingsGroup::CONTROLS,
Input::get()->getControllerNames(), Input::get()->getControllerNames(),
@@ -257,9 +281,16 @@ void ServiceMenu::initializeOptions() {
}, },
[](const std::string &val) { [](const std::string &val) {
Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER1, Input::get()->getGamepadByName(val), val); Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER1, Input::get()->getGamepadByName(val), val);
},
[this]() {
// Acción: configurar botones del mando del jugador 1
auto* gamepad = &Options::gamepad_manager.getGamepad(Player::Id::PLAYER1);
if (gamepad && gamepad->instance) {
define_buttons_->enable(gamepad);
}
})); }));
options_.push_back(std::make_unique<ListOption>( options_.push_back(std::make_unique<ActionListOption>(
Lang::getText("[SERVICE_MENU] CONTROLLER2"), Lang::getText("[SERVICE_MENU] CONTROLLER2"),
SettingsGroup::CONTROLS, SettingsGroup::CONTROLS,
Input::get()->getControllerNames(), Input::get()->getControllerNames(),
@@ -268,8 +299,16 @@ void ServiceMenu::initializeOptions() {
}, },
[](const std::string &val) { [](const std::string &val) {
Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER2, Input::get()->getGamepadByName(val), val); Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER2, Input::get()->getGamepadByName(val), val);
},
[this]() {
// Acción: configurar botones del mando del jugador 2
auto* gamepad = &Options::gamepad_manager.getGamepad(Player::Id::PLAYER2);
if (gamepad && gamepad->instance) {
define_buttons_->enable(gamepad);
}
})); }));
// CONTROLS - Opción para teclado (solo lista, sin acción)
options_.push_back(std::make_unique<ListOption>( options_.push_back(std::make_unique<ListOption>(
Lang::getText("[SERVICE_MENU] KEYBOARD"), Lang::getText("[SERVICE_MENU] KEYBOARD"),
SettingsGroup::CONTROLS, SettingsGroup::CONTROLS,
@@ -277,13 +316,16 @@ void ServiceMenu::initializeOptions() {
Lang::getText("[SERVICE_MENU] PLAYER1"), Lang::getText("[SERVICE_MENU] PLAYER1"),
Lang::getText("[SERVICE_MENU] PLAYER2")}, Lang::getText("[SERVICE_MENU] PLAYER2")},
[]() { []() {
return Lang::getNameFromCode(Options::pending_changes.new_language); // Aquí deberías devolver el jugador actual asignado al teclado
// Por ahora devuelvo "Jugador 1" como ejemplo
return Lang::getText("[SERVICE_MENU] PLAYER1");
}, },
[](const std::string &val) { [](const std::string &val) {
Options::pending_changes.new_language = Lang::getCodeFromName(val); // Aquí asignarías el teclado al jugador seleccionado
Options::checkPendingChanges(); // Implementación pendiente según tu sistema de input
})); }));
// CONTROLS - Acción para intercambiar mandos
options_.push_back(std::make_unique<ActionOption>( options_.push_back(std::make_unique<ActionOption>(
Lang::getText("[SERVICE_MENU] SWAP_CONTROLLERS"), Lang::getText("[SERVICE_MENU] SWAP_CONTROLLERS"),
SettingsGroup::CONTROLS, SettingsGroup::CONTROLS,

View File

@@ -10,6 +10,7 @@
class MenuOption; class MenuOption;
class MenuRenderer; class MenuRenderer;
class DefineButtons; // Forward declaration
class ServiceMenu { class ServiceMenu {
public: public:
@@ -52,6 +53,7 @@ class ServiceMenu {
void adjustOption(bool adjust_up); void adjustOption(bool adjust_up);
void selectOption(); void selectOption();
void moveBack(); void moveBack();
void checkEvents(const SDL_Event &event); // Nuevo método para eventos
// --- Getters para que el Renderer pueda leer el estado --- // --- Getters para que el Renderer pueda leer el estado ---
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; } [[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
@@ -81,6 +83,9 @@ class ServiceMenu {
std::unique_ptr<UIMessage> restart_message_ui_; std::unique_ptr<UIMessage> restart_message_ui_;
bool last_pending_changes_ = false; bool last_pending_changes_ = false;
// --- Configuración de botones ---
std::unique_ptr<DefineButtons> define_buttons_;
// --- La Vista --- // --- La Vista ---
std::unique_ptr<MenuRenderer> renderer_; std::unique_ptr<MenuRenderer> renderer_;
@@ -109,4 +114,4 @@ class ServiceMenu {
// --- Instancia singleton --- // --- Instancia singleton ---
static ServiceMenu *instance; static ServiceMenu *instance;
}; };

View File

@@ -0,0 +1,172 @@
#include "window_message.h"
#include <algorithm>
#include <utility>
#include "param.h"
#include "screen.h"
#include "text.h"
WindowMessage::WindowMessage(
std::shared_ptr<Text> text_renderer,
const std::string& title,
const Color& bg_color,
const Color& border_color,
const Color& title_color,
const Color& text_color
) : text_renderer_(std::move(text_renderer)),
title_(title),
bg_color_(bg_color),
border_color_(border_color),
title_color_(title_color),
text_color_(text_color) {
}
void WindowMessage::render() {
if (!visible_) {
return;
}
SDL_Renderer* renderer = Screen::get()->getRenderer();
// Dibujar fondo con transparencia
SDL_SetRenderDrawColor(renderer, bg_color_.r, bg_color_.g, bg_color_.b, bg_color_.a);
SDL_RenderFillRect(renderer, &rect_);
// Dibujar borde
SDL_SetRenderDrawColor(renderer, border_color_.r, border_color_.g, border_color_.b, border_color_.a);
SDL_RenderRect(renderer, &rect_);
float current_y = rect_.y + padding_;
// Dibujar título si existe
if (!title_.empty()) {
text_renderer_->writeCentered(
rect_.x + rect_.w / 2.0f,
current_y,
title_
//title_color_
);
current_y += text_renderer_->getCharacterSize() + line_spacing_ * 2;
// Línea separadora debajo del título
SDL_SetRenderDrawColor(renderer, border_color_.r, border_color_.g, border_color_.b, border_color_.a);
SDL_RenderLine(renderer,
rect_.x + padding_, current_y - line_spacing_,
rect_.x + rect_.w - padding_, current_y - line_spacing_);
}
// Dibujar textos
for (const auto& text : texts_) {
text_renderer_->writeCentered(
rect_.x + rect_.w / 2.0f,
current_y,
text
//text_color_
);
current_y += text_renderer_->getCharacterSize() + line_spacing_;
}
}
void WindowMessage::update() {
// Por ahora no hay animaciones, pero se puede extender
}
void WindowMessage::show() {
visible_ = true;
}
void WindowMessage::hide() {
visible_ = false;
}
void WindowMessage::setTitle(const std::string& title) {
title_ = title;
}
void WindowMessage::setText(const std::string& text) {
texts_.clear();
texts_.push_back(text);
}
void WindowMessage::setTexts(const std::vector<std::string>& texts) {
texts_ = texts;
}
void WindowMessage::addText(const std::string& text) {
texts_.push_back(text);
}
void WindowMessage::clearTexts() {
texts_.clear();
}
void WindowMessage::setPosition(float x, float y) {
rect_.x = x;
rect_.y = y;
}
void WindowMessage::setSize(float width, float height) {
rect_.w = width;
rect_.h = height;
}
void WindowMessage::centerOnScreen() {
rect_.x = (param.game.width - rect_.w) / 2.0f;
rect_.y = (param.game.height - rect_.h) / 2.0f;
}
void WindowMessage::autoSize() {
calculateAutoSize();
}
void WindowMessage::calculateAutoSize() {
float content_width = calculateContentWidth();
float content_height = calculateContentHeight();
rect_.w = content_width + (padding_ * 2);
rect_.h = content_height + (padding_ * 2);
// Aplicar límites mínimos y máximos
rect_.w = std::max(rect_.w, 200.0f);
rect_.h = std::max(rect_.h, 100.0f);
// No exceder el 80% de la pantalla
rect_.w = std::min(rect_.w, param.game.width * 0.8f);
rect_.h = std::min(rect_.h, param.game.height * 0.8f);
}
auto WindowMessage::calculateContentHeight() const -> float {
float height = 0;
// Altura del título
if (!title_.empty()) {
height += text_renderer_->getCharacterSize() + line_spacing_ * 2; // Espacio extra para separador
}
// Altura de los textos
if (!texts_.empty()) {
height += (texts_.size() * text_renderer_->getCharacterSize());
height += ((texts_.size() - 1) * line_spacing_); // Espaciado entre líneas
}
return height;
}
auto WindowMessage::calculateContentWidth() const -> float {
float max_width = 200.0f; // Ancho mínimo
// Ancho del título
if (!title_.empty()) {
float title_width = text_renderer_->lenght(title_, -2);
max_width = std::max(max_width, title_width);
}
// Ancho de los textos
for (const auto& text : texts_) {
float text_width = text_renderer_->lenght(text, -2);
max_width = std::max(max_width, text_width);
}
return max_width;
}

View File

@@ -0,0 +1,84 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory>
#include <string>
#include <vector>
#include "color.h"
class Text;
class WindowMessage {
public:
WindowMessage(
std::shared_ptr<Text> text_renderer,
const std::string& title = "",
const Color& bg_color = Color{40, 40, 60, 220},
const Color& border_color = Color{100, 100, 120, 255},
const Color& title_color = Color{255, 255, 255, 255},
const Color& text_color = Color{200, 200, 200, 255}
);
// Métodos principales
void render();
void update();
// Control de visibilidad
void show();
void hide();
[[nodiscard]] auto isVisible() const -> bool { return visible_; }
// Configuración de contenido
void setTitle(const std::string& title);
void setText(const std::string& text);
void setTexts(const std::vector<std::string>& texts);
void addText(const std::string& text);
void clearTexts();
// Configuración de posición y tamaño
void setPosition(float x, float y);
void setSize(float width, float height);
void centerOnScreen();
void autoSize(); // Ajusta automáticamente al contenido
// Configuración de colores
void setBackgroundColor(const Color& color) { bg_color_ = color; }
void setBorderColor(const Color& color) { border_color_ = color; }
void setTitleColor(const Color& color) { title_color_ = color; }
void setTextColor(const Color& color) { text_color_ = color; }
// Configuración de espaciado
void setPadding(float padding) { padding_ = padding; }
void setLineSpacing(float spacing) { line_spacing_ = spacing; }
// Getters
[[nodiscard]] auto getRect() const -> const SDL_FRect& { return rect_; }
private:
std::shared_ptr<Text> text_renderer_;
// Estado de visibilidad
bool visible_ = false;
// Contenido
std::string title_;
std::vector<std::string> texts_;
// Posición y tamaño
SDL_FRect rect_{0, 0, 300, 200};
float padding_ = 15.0f;
float line_spacing_ = 5.0f;
// Colores
Color bg_color_;
Color border_color_;
Color title_color_;
Color text_color_;
// Métodos privados
void calculateAutoSize();
[[nodiscard]] auto calculateContentHeight() const -> float;
[[nodiscard]] auto calculateContentWidth() const -> float;
};