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

@@ -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 {
public:
enum class Behavior {
ADJUST,
SELECT
ADJUST, // Solo puede ajustar valor (como IntOption, BoolOption, ListOption)
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)

View File

@@ -45,27 +45,39 @@ void MenuRenderer::render(const ServiceMenu *menu_state) {
// Dibuja las opciones
y = options_y_;
const auto &option_pairs = menu_state->getOptionPairs();
const auto &display_options = menu_state->getDisplayOptions();
for (size_t i = 0; i < option_pairs.size(); ++i) {
const bool IS_SELECTED = (i == menu_state->getSelectedIndex());
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);
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;
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);
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);
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 {
// 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);
const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
std::string truncated_caption = getTruncatedValue(option_pairs.at(i).first, 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_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_;
}

View File

@@ -16,6 +16,8 @@
#include "section.hpp" // Para Name, name, Options, options
#include "ui/ui_message.h" // Para UIMessage
#include "utils.h" // Para Zone
#include "action_list_option.h" // Para ActionListOption
#include "define_buttons.h" // Para DefineButtons
// Singleton
ServiceMenu *ServiceMenu::instance = nullptr;
@@ -32,6 +34,7 @@ ServiceMenu::ServiceMenu()
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);
define_buttons_ = std::make_unique<DefineButtons>();
reset();
}
@@ -55,6 +58,11 @@ void ServiceMenu::render() {
const float MSG_Y = renderer_->getRect().y + 39.0F;
restart_message_ui_->setPosition(MSG_X, MSG_Y);
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() {
@@ -70,6 +78,22 @@ void ServiceMenu::update() {
last_pending_changes_ = now_pending;
}
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() {
@@ -247,8 +271,8 @@ auto ServiceMenu::countOptionsInGroup(SettingsGroup group) const -> size_t {
void ServiceMenu::initializeOptions() {
options_.clear();
// CONTROLS
options_.push_back(std::make_unique<ListOption>(
// CONTROLS - Usando ActionListOption para mandos
options_.push_back(std::make_unique<ActionListOption>(
Lang::getText("[SERVICE_MENU] CONTROLLER1"),
SettingsGroup::CONTROLS,
Input::get()->getControllerNames(),
@@ -257,9 +281,16 @@ void ServiceMenu::initializeOptions() {
},
[](const std::string &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"),
SettingsGroup::CONTROLS,
Input::get()->getControllerNames(),
@@ -268,8 +299,16 @@ void ServiceMenu::initializeOptions() {
},
[](const std::string &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>(
Lang::getText("[SERVICE_MENU] KEYBOARD"),
SettingsGroup::CONTROLS,
@@ -277,13 +316,16 @@ void ServiceMenu::initializeOptions() {
Lang::getText("[SERVICE_MENU] PLAYER1"),
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) {
Options::pending_changes.new_language = Lang::getCodeFromName(val);
Options::checkPendingChanges();
// Aquí asignarías el teclado al jugador seleccionado
// Implementación pendiente según tu sistema de input
}));
// CONTROLS - Acción para intercambiar mandos
options_.push_back(std::make_unique<ActionOption>(
Lang::getText("[SERVICE_MENU] SWAP_CONTROLLERS"),
SettingsGroup::CONTROLS,

View File

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