key_config.cpp i keys.yaml per a centralitzar i no hardcodejar tecles
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}};
|
||||
|
||||
|
||||
126
source/core/input/key_config.cpp
Normal file
126
source/core/input/key_config.cpp
Normal 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];
|
||||
}
|
||||
53
source/core/input/key_config.hpp
Normal file
53
source/core/input/key_config.hpp
Normal 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_
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user