feat(service_menu): picker de mando per llista i fix SENSE MANDO
El cycle anterior fallava al desasignar perque Input::resolvePlayerGamepad tenia un fallback per slot que reasignava gamepads_[player_index] quan name+path eren buits. Això el contradeia el slot "SENSE MANDO" del cycle: el YAML quedava buit pero el runtime seguia lligant el mando. Treure el fallback i moure l'autoassignacio inicial al boot (nomes si tots dos jugadors venen buits) restaura la semàntica: buit vol dir buit. Sobre el fix, redissenyem la UX dels items MANDO P1/P2: ja no son CYCLE sino SUBMENU que obrin una pàgina-llista (estil RESOLUCIÓ) amb tots els mandos detectats. Cada item porta sufix (P1)/(P2) nomes si el mando el te l'altre jugador, perque sapigues que assignar-lo li'l "robarà". L'ultim item es "SENSE MANDO" per a desassignar explícitament. La lògica de swap automatic en conflicte queda extreta a assignPadToPlayer i es reutilitza des de la picker. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -121,19 +121,11 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
// Avança ciclicament l'assignacio de pad d'un jugador i la persisteix.
|
||||
// El cycle inclou un slot virtual "sense mando" al final (NEXT == N).
|
||||
// Si l'altre jugador ja tenia el pad triat, fa swap: l'altre rep la
|
||||
// assignacio prèvia d'aquest jugador.
|
||||
void cyclePlayerPad(int player_index, int dir) {
|
||||
const auto* input = Input::get();
|
||||
if (input == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto& pads = input->getGamepads();
|
||||
if (pads.empty()) {
|
||||
return;
|
||||
}
|
||||
// Assigna un pad concret (per nom+path) a un jugador i ho persisteix.
|
||||
// Si l'altre jugador ja tenia eixe pad, fa swap: l'altre rep
|
||||
// l'assignacio prèvia d'aquest. Per desasignar, passar new_name i
|
||||
// new_path buits.
|
||||
void assignPadToPlayer(int player_index, const std::string& new_name, const std::string& new_path) {
|
||||
auto& pcfg = (player_index == 0)
|
||||
? ConfigYaml::engine_config.player1
|
||||
: ConfigYaml::engine_config.player2;
|
||||
@@ -141,21 +133,9 @@ namespace {
|
||||
? ConfigYaml::engine_config.player2
|
||||
: ConfigYaml::engine_config.player1;
|
||||
|
||||
const std::size_t N = pads.size();
|
||||
const std::size_t SLOTS = N + 1; // N pads + slot "sense mando"
|
||||
const std::size_t CURRENT = findAssignedIndex(pads, pcfg);
|
||||
const std::size_t STEP = (dir > 0) ? 1 : (SLOTS - 1);
|
||||
const std::size_t NEXT = (CURRENT + STEP) % SLOTS;
|
||||
|
||||
// Determinem el nou nom + path (buits si seleccionem "sense mando").
|
||||
std::string new_name;
|
||||
std::string new_path;
|
||||
if (NEXT < N && pads[NEXT]) {
|
||||
new_name = pads[NEXT]->name;
|
||||
new_path = pads[NEXT]->path;
|
||||
}
|
||||
|
||||
// Detecta conflicte amb l'altre jugador per fer swap.
|
||||
// Detecta conflicte amb l'altre jugador per fer swap. Prioritzem
|
||||
// path (mateix criteri que resolvePlayerGamepad); si nomes tenim
|
||||
// nom (mando reconegut sense path), comparem per nom.
|
||||
const bool CONFLICT = !new_path.empty() && other.gamepad_path == new_path;
|
||||
const bool CONFLICT_BY_NAME = !new_name.empty() && new_path.empty() &&
|
||||
other.gamepad_name == new_name;
|
||||
@@ -554,18 +534,6 @@ namespace System {
|
||||
|
||||
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::Item {
|
||||
return ServiceMenu::Item{
|
||||
.kind = ServiceMenu::Kind::ACTION,
|
||||
@@ -587,8 +555,24 @@ namespace System {
|
||||
Page page;
|
||||
page.title_key = "service_menu.controls";
|
||||
page.items = {
|
||||
makeCyclePadItem("service_menu.controls_pad_p1", 0),
|
||||
makeCyclePadItem("service_menu.controls_pad_p2", 1),
|
||||
Item{
|
||||
.kind = Kind::SUBMENU,
|
||||
.label_key = "service_menu.controls_pad_p1",
|
||||
.label_text = {},
|
||||
.selectable = true,
|
||||
.on_activate = [this] { pushPage(buildPadPickerPage(0)); },
|
||||
.get_value_text = [] { return padDisplayName(0); },
|
||||
.on_change = {},
|
||||
},
|
||||
Item{
|
||||
.kind = Kind::SUBMENU,
|
||||
.label_key = "service_menu.controls_pad_p2",
|
||||
.label_text = {},
|
||||
.selectable = true,
|
||||
.on_activate = [this] { pushPage(buildPadPickerPage(1)); },
|
||||
.get_value_text = [] { return padDisplayName(1); },
|
||||
.on_change = {},
|
||||
},
|
||||
makeDefineItem("service_menu.controls_define_keyboard_p1",
|
||||
DefineInputs::Mode::KEYBOARD,
|
||||
DefineInputs::Player::P1),
|
||||
@@ -605,6 +589,70 @@ namespace System {
|
||||
return page;
|
||||
}
|
||||
|
||||
auto ServiceMenu::buildPadPickerPage(int player_index) -> Page {
|
||||
Page page;
|
||||
page.title_key = (player_index == 0)
|
||||
? "service_menu.controls_pad_p1"
|
||||
: "service_menu.controls_pad_p2";
|
||||
|
||||
const auto* input = Input::get();
|
||||
if (input == nullptr) {
|
||||
return page;
|
||||
}
|
||||
const auto& pads = input->getGamepads();
|
||||
const auto& pcfg = (player_index == 0)
|
||||
? ConfigYaml::engine_config.player1
|
||||
: ConfigYaml::engine_config.player2;
|
||||
const auto& other = (player_index == 0)
|
||||
? ConfigYaml::engine_config.player2
|
||||
: ConfigYaml::engine_config.player1;
|
||||
|
||||
// Cursor inicial sobre el pad assignat, o sobre "SENSE MANDO"
|
||||
// (ultim item) si el jugador no en te cap.
|
||||
page.cursor = findAssignedIndex(pads, pcfg);
|
||||
|
||||
for (const auto& pad : pads) {
|
||||
if (!pad) {
|
||||
continue;
|
||||
}
|
||||
std::string label = Utils::toUpperAscii(pad->name);
|
||||
// Sufix (PX) nomes si el mando el te l'altre jugador, perque
|
||||
// l'usuari sapiga que assignar-lo li'l "robarà".
|
||||
const bool OTHER_HAS_BY_PATH = !other.gamepad_path.empty() && other.gamepad_path == pad->path;
|
||||
const bool OTHER_HAS_BY_NAME = other.gamepad_path.empty() && !other.gamepad_name.empty() && other.gamepad_name == pad->name;
|
||||
if (OTHER_HAS_BY_PATH || OTHER_HAS_BY_NAME) {
|
||||
label += (player_index == 0) ? " (P2)" : " (P1)";
|
||||
}
|
||||
const std::string PAD_NAME = pad->name;
|
||||
const std::string PAD_PATH = pad->path;
|
||||
const int PI = player_index;
|
||||
page.items.push_back(Item{
|
||||
.kind = Kind::ACTION,
|
||||
.label_key = {},
|
||||
.label_text = std::move(label),
|
||||
.selectable = true,
|
||||
.on_activate = [PI, PAD_NAME, PAD_PATH] {
|
||||
assignPadToPlayer(PI, PAD_NAME, PAD_PATH);
|
||||
},
|
||||
.get_value_text = {},
|
||||
.on_change = {},
|
||||
});
|
||||
}
|
||||
|
||||
// Item final: desasignar.
|
||||
const int PI = player_index;
|
||||
page.items.push_back(Item{
|
||||
.kind = Kind::ACTION,
|
||||
.label_key = "service_menu.controls_no_pad",
|
||||
.label_text = {},
|
||||
.selectable = true,
|
||||
.on_activate = [PI] { assignPadToPlayer(PI, {}, {}); },
|
||||
.get_value_text = {},
|
||||
.on_change = {},
|
||||
});
|
||||
return page;
|
||||
}
|
||||
|
||||
auto ServiceMenu::buildSystemPage() -> Page {
|
||||
Page page;
|
||||
page.title_key = "service_menu.system";
|
||||
|
||||
Reference in New Issue
Block a user