feat(service_menu): pagina CONTROLS amb assignacio de pad i rebind per jugador
Afegeix submenu CONTROLS al menu de servei amb 2 items CYCLE per seleccionar el mando assignat a cada jugador (persistit per name + path) i 4 items ACTION per arrancar DefineInputs (teclat/mando per a P1/P2). Tambe afegeix: - Director: init/update/draw/destroy del singleton DefineInputs. - GlobalEvents: routing prioritari de tots els events a DefineInputs mentre l'overlay esta actiu. - Locale ca/en: claus del submenu CONTROLS i de l'overlay de rebind. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,9 +15,12 @@
|
||||
#include "core/defaults/audio.hpp"
|
||||
#include "core/defaults/rendering.hpp"
|
||||
#include "core/defaults/service_menu.hpp"
|
||||
#include "core/input/define_inputs.hpp"
|
||||
#include "core/input/input.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/rendering/sdl_manager.hpp"
|
||||
#include "core/system/debug_overlay.hpp"
|
||||
#include "core/system/notifier.hpp"
|
||||
#include "core/system/relaunch.hpp"
|
||||
#include "core/types.hpp"
|
||||
#include "game/config_yaml.hpp"
|
||||
@@ -77,6 +80,84 @@ namespace {
|
||||
return Locale::get().text(item.label_key);
|
||||
}
|
||||
|
||||
// ---- Helpers de la pagina CONTROLS ----
|
||||
|
||||
auto padDisplayName(int player_index) -> std::string {
|
||||
const auto* input = Input::get();
|
||||
if (input == nullptr) {
|
||||
return Locale::get().text("service_menu.controls_no_pad");
|
||||
}
|
||||
auto pad = input->getPlayerGamepad(player_index);
|
||||
if (!pad) {
|
||||
return Locale::get().text("service_menu.controls_no_pad");
|
||||
}
|
||||
return pad->name;
|
||||
}
|
||||
|
||||
// Index actual del pad assignat dins de la llista de mandos detectats.
|
||||
// Prioritat path > name. Si no n'hi ha cap match, retorna 0.
|
||||
auto findAssignedIndex(const std::vector<std::shared_ptr<Input::Gamepad>>& pads,
|
||||
const Config::PlayerBindings& pcfg) -> std::size_t {
|
||||
for (std::size_t i = 0; i < pads.size(); ++i) {
|
||||
if (pads[i] && !pcfg.gamepad_path.empty() && pads[i]->path == pcfg.gamepad_path) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0; i < pads.size(); ++i) {
|
||||
if (pads[i] && !pcfg.gamepad_name.empty() && pads[i]->name == pcfg.gamepad_name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Avança ciclicament l'assignacio de pad d'un jugador i la persisteix.
|
||||
void cyclePlayerPad(int player_index, int dir) {
|
||||
auto* input = Input::get();
|
||||
if (input == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto& pads = input->getGamepads();
|
||||
if (pads.empty()) {
|
||||
return;
|
||||
}
|
||||
auto& pcfg = (player_index == 0)
|
||||
? ConfigYaml::engine_config.player1
|
||||
: ConfigYaml::engine_config.player2;
|
||||
|
||||
const std::size_t CURRENT = findAssignedIndex(pads, pcfg);
|
||||
const std::size_t N = pads.size();
|
||||
const std::size_t STEP = (dir > 0) ? 1 : (N - 1);
|
||||
const std::size_t NEXT = (CURRENT + STEP) % N;
|
||||
if (!pads[NEXT]) {
|
||||
return;
|
||||
}
|
||||
pcfg.gamepad_name = pads[NEXT]->name;
|
||||
pcfg.gamepad_path = pads[NEXT]->path;
|
||||
if (player_index == 0) {
|
||||
input->applyPlayer1Bindings(ConfigYaml::engine_config.player1);
|
||||
} else {
|
||||
input->applyPlayer2Bindings(ConfigYaml::engine_config.player2);
|
||||
}
|
||||
ConfigYaml::saveToFile();
|
||||
}
|
||||
|
||||
// Arranca un rebind o avisa si el jugador no te pad. Retorna true si el
|
||||
// rebind ha començat (el caller ha de tancar el menu).
|
||||
auto startDefine(System::DefineInputs::Mode mode, System::DefineInputs::Player pl) -> bool {
|
||||
auto* di = System::DefineInputs::get();
|
||||
if (di == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!di->begin(mode, pl)) {
|
||||
if (auto* n = System::Notifier::get(); n != nullptr) {
|
||||
n->notifyWarn(Locale::get().text("define.no_gamepad"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace System {
|
||||
@@ -153,6 +234,7 @@ namespace System {
|
||||
makeSubmenu("service_menu.video", [this] { pushPage(buildVideoPage()); }),
|
||||
makeSubmenu("service_menu.audio", [this] { pushPage(buildAudioPage()); }),
|
||||
makeSubmenu("service_menu.options", [this] { pushPage(buildOptionsPage()); }),
|
||||
makeSubmenu("service_menu.controls", [this] { pushPage(buildControlsPage()); }),
|
||||
makeSubmenu("service_menu.system", [this] { pushPage(buildSystemPage()); }),
|
||||
};
|
||||
stack_.clear();
|
||||
@@ -437,6 +519,63 @@ namespace System {
|
||||
return page;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
auto makeCyclePadItem(const char* label_key, int player_index) -> ServiceMenu::Item {
|
||||
return ServiceMenu::Item{
|
||||
.kind = ServiceMenu::Kind::CYCLE,
|
||||
.label_key = label_key,
|
||||
.label_text = {},
|
||||
.selectable = true,
|
||||
.on_activate = {},
|
||||
.get_value_text = [player_index] { return padDisplayName(player_index); },
|
||||
.on_change = [player_index](int dir) { cyclePlayerPad(player_index, dir); },
|
||||
};
|
||||
}
|
||||
|
||||
auto makeDefineItem(const char* label_key, DefineInputs::Mode mode, DefineInputs::Player pl, ServiceMenu* menu) -> ServiceMenu::Item {
|
||||
return ServiceMenu::Item{
|
||||
.kind = ServiceMenu::Kind::ACTION,
|
||||
.label_key = label_key,
|
||||
.label_text = {},
|
||||
.selectable = true,
|
||||
.on_activate = [mode, pl, menu] {
|
||||
if (startDefine(mode, pl) && menu != nullptr && menu->isOpen()) {
|
||||
menu->toggle();
|
||||
} },
|
||||
.get_value_text = {},
|
||||
.on_change = {},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto ServiceMenu::buildControlsPage() -> Page {
|
||||
Page page;
|
||||
page.title_key = "service_menu.controls";
|
||||
page.items = {
|
||||
makeCyclePadItem("service_menu.controls_pad_p1", 0),
|
||||
makeCyclePadItem("service_menu.controls_pad_p2", 1),
|
||||
makeDefineItem("service_menu.controls_define_keyboard_p1",
|
||||
DefineInputs::Mode::KEYBOARD,
|
||||
DefineInputs::Player::P1,
|
||||
this),
|
||||
makeDefineItem("service_menu.controls_define_keyboard_p2",
|
||||
DefineInputs::Mode::KEYBOARD,
|
||||
DefineInputs::Player::P2,
|
||||
this),
|
||||
makeDefineItem("service_menu.controls_define_gamepad_p1",
|
||||
DefineInputs::Mode::GAMEPAD,
|
||||
DefineInputs::Player::P1,
|
||||
this),
|
||||
makeDefineItem("service_menu.controls_define_gamepad_p2",
|
||||
DefineInputs::Mode::GAMEPAD,
|
||||
DefineInputs::Player::P2,
|
||||
this),
|
||||
};
|
||||
return page;
|
||||
}
|
||||
|
||||
auto ServiceMenu::buildSystemPage() -> Page {
|
||||
Page page;
|
||||
page.title_key = "service_menu.system";
|
||||
|
||||
Reference in New Issue
Block a user