key_config.cpp i keys.yaml per a centralitzar i no hardcodejar tecles

This commit is contained in:
2026-04-12 15:21:43 +02:00
parent 999cf53821
commit 848b658611
19 changed files with 521 additions and 115 deletions

View File

@@ -7,9 +7,9 @@
#include "core/input/input.hpp" // Para Input, InputAction, Input::DO_NOT_ALLOW_REPEAT
#include "core/locale/locale.hpp" // Para Locale
#include "core/rendering/render_info.hpp" // Para RenderInfo
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/screenshot.hpp" // Para Screenshot
#include "core/rendering/render_info.hpp" // Para RenderInfo
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/screenshot.hpp" // Para Screenshot
#ifdef _DEBUG
#include "core/system/debug.hpp" // Para Debug (persistencia de render_info en debug.yaml)
#endif

View File

@@ -27,34 +27,17 @@ auto Input::get() -> Input* { return Input::instance; }
// Constructor
Input::Input(std::string game_controller_db_path)
: gamepad_mappings_file_(std::move(game_controller_db_path)) {
// Inicializar bindings del teclado
// Bindings de gameplay (rebindeables por el jugador vía Options)
// Las teclas de sistema (F1-F12, B, S, `, Esc...) se cargan desde keys.yaml
// vía KeyConfig::applyGlobalBindings() — NO duplicar aquí.
keyboard_.bindings = {
// Movimiento del jugador
{Action::LEFT, KeyState{.scancode = SDL_SCANCODE_LEFT}},
{Action::RIGHT, KeyState{.scancode = SDL_SCANCODE_RIGHT}},
{Action::JUMP, KeyState{.scancode = SDL_SCANCODE_UP}},
{Action::DOWN, KeyState{.scancode = SDL_SCANCODE_DOWN}},
// Inputs de control
{Action::ACCEPT, KeyState{.scancode = SDL_SCANCODE_RETURN}},
{Action::CANCEL, KeyState{.scancode = SDL_SCANCODE_ESCAPE}},
{Action::EXIT, KeyState{.scancode = SDL_SCANCODE_ESCAPE}},
// Inputs de sistema
{Action::WINDOW_DEC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F1}},
{Action::WINDOW_INC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F2}},
{Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}},
{Action::TOGGLE_SHADER, KeyState{.scancode = SDL_SCANCODE_F4}},
{Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}},
{Action::NEXT_PALETTE_SORT, KeyState{.scancode = SDL_SCANCODE_F6}},
{Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}},
{Action::TOGGLE_IN_GAME_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}},
{Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}},
{Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}},
{Action::PAUSE, KeyState{.scancode = SDL_SCANCODE_F11}},
{Action::TOGGLE_INFO, KeyState{.scancode = SDL_SCANCODE_F12}},
{Action::TOGGLE_CONSOLE, KeyState{.scancode = SDL_SCANCODE_GRAVE}},
{Action::SCREENSHOT, KeyState{.scancode = SDL_SCANCODE_S}}};
};
initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD
}

View File

@@ -24,7 +24,9 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
{InputAction::NEXT_PALETTE_SORT, "NEXT_PALETTE_SORT"},
{InputAction::TOGGLE_SHADER, "TOGGLE_POSTFX"},
{InputAction::NEXT_SHADER_PRESET, "NEXT_POSTFX_PRESET"},
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
{InputAction::TOGGLE_INFO, "TOGGLE_DEBUG"},
{InputAction::TOGGLE_CONSOLE, "TOGGLE_CONSOLE"},
{InputAction::SCREENSHOT, "SCREENSHOT"},
{InputAction::NONE, "NONE"}};
@@ -49,7 +51,9 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
{"NEXT_PALETTE_SORT", InputAction::NEXT_PALETTE_SORT},
{"TOGGLE_POSTFX", InputAction::TOGGLE_SHADER},
{"NEXT_POSTFX_PRESET", InputAction::NEXT_SHADER_PRESET},
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
{"TOGGLE_DEBUG", InputAction::TOGGLE_INFO},
{"TOGGLE_CONSOLE", InputAction::TOGGLE_CONSOLE},
{"SCREENSHOT", InputAction::SCREENSHOT},
{"NONE", InputAction::NONE}};

View File

@@ -0,0 +1,126 @@
#include "core/input/key_config.hpp"
#include <iostream> // Para cerr
#include <string> // Para string
#include <utility> // Para move
#include <vector> // Para vector
#include "core/input/input.hpp" // Para Input
#include "core/input/input_types.hpp" // Para STRING_TO_ACTION
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
#include "external/fkyaml_node.hpp" // Para fkyaml::node
// ── Singleton ────────────────────────────────────────────────────────────────
KeyConfig* KeyConfig::instance_ = nullptr;
void KeyConfig::init(const std::string& yaml_path) {
instance_ = new KeyConfig();
instance_->load(yaml_path);
}
void KeyConfig::destroy() {
delete instance_;
instance_ = nullptr;
}
auto KeyConfig::get() -> KeyConfig* { return instance_; }
// ── Carga del YAML ──────────────────────────────────────────────────────────
void KeyConfig::load(const std::string& yaml_path) {
auto file_data = Resource::Helper::loadFile(yaml_path);
if (file_data.empty()) {
std::cerr << "KeyConfig: Unable to load " << yaml_path << '\n';
return;
}
std::string yaml_content(file_data.begin(), file_data.end());
fkyaml::node yaml;
try {
yaml = fkyaml::node::deserialize(yaml_content);
} catch (const fkyaml::exception& e) {
std::cerr << "KeyConfig: YAML parse error: " << e.what() << '\n';
return;
}
if (!yaml.contains("scopes")) { return; }
for (const auto& scope_node : yaml["scopes"]) {
KeyConfigScope scope;
scope.name = scope_node["name"].get_value<std::string>();
if (scope_node.contains("keys")) {
for (const auto& key_node : scope_node["keys"]) {
KeyEntry entry;
entry.id = key_node["id"].get_value<std::string>();
entry.display_key = key_node["key"].get_value<std::string>();
entry.desc = key_node["desc"].get_value<std::string>();
// Convertir el nombre SDL a keycode
if (key_node.contains("code")) {
auto code = key_node["code"].get_value<std::string>();
entry.keycode = SDL_GetKeyFromName(code.c_str());
if (entry.keycode == SDLK_UNKNOWN) {
std::cerr << "KeyConfig: Unknown key name '" << code
<< "' for " << scope.name << "." << entry.id << '\n';
}
}
// InputAction opcional (para scope GLOBAL)
if (key_node.contains("action")) {
entry.action = key_node["action"].get_value<std::string>();
}
scope.index[entry.id] = scope.entries.size();
scope.entries.push_back(std::move(entry));
}
}
scope_index_[scope.name] = scopes_.size();
scopes_.push_back(std::move(scope));
}
std::cout << "KeyConfig: Loaded " << scopes_.size() << " scopes from " << yaml_path << '\n';
}
// ── Consultas ───────────────────────────────────────────────────────────────
auto KeyConfig::key(const std::string& scope, const std::string& id) const -> SDL_Keycode {
const auto* s = getScope(scope);
if (s == nullptr) { return SDLK_UNKNOWN; }
auto it = s->index.find(id);
if (it == s->index.end()) { return SDLK_UNKNOWN; }
return s->entries[it->second].keycode;
}
void KeyConfig::applyGlobalBindings() const {
const auto* global = getScope("GLOBAL");
if (global == nullptr) { return; }
for (const auto& entry : global->entries) {
if (entry.action.empty() || entry.keycode == SDLK_UNKNOWN) { continue; }
auto it = STRING_TO_ACTION.find(entry.action);
if (it == STRING_TO_ACTION.end()) {
std::cerr << "KeyConfig: Unknown action '" << entry.action << "' for " << entry.id << '\n';
continue;
}
// Convertir keycode a scancode para el sistema de Input
SDL_Scancode scancode = SDL_GetScancodeFromKey(entry.keycode, nullptr);
if (scancode != SDL_SCANCODE_UNKNOWN) {
Input::get()->bindKey(it->second, scancode);
}
}
}
auto KeyConfig::getScopes() const -> const std::vector<KeyConfigScope>& {
return scopes_;
}
auto KeyConfig::getScope(const std::string& name) const -> const KeyConfigScope* {
auto it = scope_index_.find(name);
if (it == scope_index_.end()) { return nullptr; }
return &scopes_[it->second];
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <SDL3/SDL.h>
#include <string> // Para string
#include <unordered_map> // Para unordered_map
#include <vector> // Para vector
// Entrada de tecla cargada desde keys.yaml
struct KeyEntry {
std::string id; // Identificador usado en código (ej. "grid", "tile_picker")
std::string display_key; // Texto para mostrar en HELP KEYS (ej. "G", "T")
std::string desc; // Descripción corta
SDL_Keycode keycode{SDLK_UNKNOWN}; // Tecla SDL asignada
std::string action; // (Opcional) Nombre de InputAction para scope GLOBAL
};
// Grupo de teclas por ámbito
struct KeyConfigScope {
std::string name; // "GLOBAL", "EDITOR", "MINIMAP", "DEBUG"
std::vector<KeyEntry> entries;
std::unordered_map<std::string, size_t> index; // id → posición en entries
};
// Registro centralizado de teclas — fuente única de verdad (keys.yaml)
class KeyConfig {
public:
// Singleton
static void init(const std::string& yaml_path);
static void destroy();
static auto get() -> KeyConfig*;
// Consulta la SDL_Keycode asignada a un id dentro de un scope
[[nodiscard]] auto key(const std::string& scope, const std::string& id) const -> SDL_Keycode;
// Aplica las teclas del scope GLOBAL al sistema de Input (Input::bindKey)
void applyGlobalBindings() const;
// Acceso a scopes para HELP KEYS y para aplicar bindings globales
[[nodiscard]] auto getScopes() const -> const std::vector<KeyConfigScope>&;
[[nodiscard]] auto getScope(const std::string& name) const -> const KeyConfigScope*;
private:
static KeyConfig* instance_;
KeyConfig() = default;
~KeyConfig() = default;
void load(const std::string& yaml_path);
std::vector<KeyConfigScope> scopes_;
std::unordered_map<std::string, size_t> scope_index_; // nombre → posición en scopes_
};

View File

@@ -9,9 +9,8 @@
#include <vector> // Para vector
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "external/stb_image_write.h" // Para stbi_write_png
#include "core/rendering/surface.hpp" // Para Surface
#include "external/stb_image_write.h" // Para stbi_write_png
namespace Screenshot {

View File

@@ -13,6 +13,7 @@
#include "core/audio/audio.hpp" // Para Audio
#include "core/input/input.hpp" // Para Input, InputAction
#include "core/input/key_config.hpp" // Para KeyConfig
#include "core/locale/locale.hpp" // Para Locale
#include "core/rendering/render_info.hpp" // Para RenderInfo
#include "core/rendering/screen.hpp" // Para Screen
@@ -186,6 +187,9 @@ Director::Director() {
Console::init("8bithud");
Screen::get()->setNotificationsEnabled(true);
// Cargar configuración de teclas desde YAML (fuente única de verdad)
KeyConfig::init("data/input/keys.yaml");
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
#ifdef RELEASE_BUILD
// In release, construct the path manually (not from Asset which has empty executable_path)
@@ -196,6 +200,9 @@ Director::Director() {
Input::init(Resource::List::get()->get("gamecontrollerdb.txt")); // Carga configuración de controles
#endif
// Aplicar teclas globales desde KeyConfig al sistema de Input
KeyConfig::get()->applyGlobalBindings();
// Aplica las teclas y botones del gamepad configurados desde Options
Input::get()->applyKeyboardBindingsFromOptions();
Input::get()->applyGamepadBindingsFromOptions();
@@ -246,6 +253,7 @@ Director::~Director() {
Debug::destroy();
#endif
Input::destroy();
KeyConfig::destroy();
Console::destroy();
RenderInfo::destroy();
Notifier::destroy();