refactor: extreure helpers per reduir complexitat cognitiva (tidy net)

This commit is contained in:
2026-05-16 16:13:57 +02:00
parent b984e6041e
commit e1bc1b597f
31 changed files with 1145 additions and 1332 deletions
+126 -100
View File
@@ -60,10 +60,10 @@ namespace Menu {
const char* label;
ItemKind kind;
std::function<std::string()> get_value; // opcional
std::function<void(int dir)> change; // per TOGGLE/CYCLE/INT_RANGE
std::function<void()> enter; // per SUBMENU i ACTION
SDL_Scancode* scancode{nullptr}; // per KEY_BIND
std::function<bool()> visible; // nullptr ⇒ sempre visible
std::function<void(int dir)> change; // per TOGGLE/CYCLE/INT_RANGE
std::function<void()> enter; // per SUBMENU i ACTION
SDL_Scancode* scancode{nullptr}; // per KEY_BIND
std::function<bool()> visible; // nullptr ⇒ sempre visible
};
struct Page {
@@ -448,27 +448,30 @@ namespace Menu {
capturing = nullptr;
}
void handleKey(SDL_Scancode sc) {
if (!isOpen()) {
return;
static void backOrClose() {
if (stack.size() > 1) {
popPage();
} else {
close();
}
Page& page = stack.back();
if (page.items.empty()) {
// Pàgina buida — només backspace surt
if (sc == SDL_SCANCODE_BACKSPACE) {
if (stack.size() > 1) {
popPage();
} else {
close();
}
}
// Activació d'un ítem (RETURN/KP_ENTER): SUBMENU/ACTION criden enter,
// KEY_BIND inicia captura, la resta avança change(+1).
static void activateItem(Item& item) {
if (item.kind == ItemKind::SUBMENU || item.kind == ItemKind::ACTION) {
if (item.enter) {
item.enter();
}
return;
}
// Si el cursor està sobre un ítem ocultat (p. ex. una acció anterior el va ocultar),
// reubica'l al pròxim visible abans de processar l'entrada.
if (!isVisible(page.items[page.cursor])) {
page.cursor = nextVisibleCursor(page, page.cursor, +1);
} else if (item.kind == ItemKind::KEY_BIND) {
capturing = item.scancode;
} else if (item.change) {
item.change(+1);
}
}
static void applyKeyToItem(Page& page, SDL_Scancode sc) {
Item& item = page.items[page.cursor];
switch (sc) {
case SDL_SCANCODE_UP:
page.cursor = nextVisibleCursor(page, page.cursor, -1);
@@ -477,43 +480,44 @@ namespace Menu {
page.cursor = nextVisibleCursor(page, page.cursor, +1);
break;
case SDL_SCANCODE_LEFT:
if (page.items[page.cursor].kind != ItemKind::SUBMENU &&
page.items[page.cursor].change) {
page.items[page.cursor].change(-1);
if (item.kind != ItemKind::SUBMENU && item.change) {
item.change(-1);
}
break;
case SDL_SCANCODE_RIGHT:
if (page.items[page.cursor].kind != ItemKind::SUBMENU &&
page.items[page.cursor].change) {
page.items[page.cursor].change(+1);
if (item.kind != ItemKind::SUBMENU && item.change) {
item.change(+1);
}
break;
case SDL_SCANCODE_RETURN:
case SDL_SCANCODE_KP_ENTER:
if (page.items[page.cursor].kind == ItemKind::SUBMENU ||
page.items[page.cursor].kind == ItemKind::ACTION) {
if (page.items[page.cursor].enter) {
page.items[page.cursor].enter();
}
} else if (page.items[page.cursor].kind == ItemKind::KEY_BIND) {
capturing = page.items[page.cursor].scancode;
} else if (page.items[page.cursor].change) {
page.items[page.cursor].change(+1);
}
activateItem(item);
break;
case SDL_SCANCODE_BACKSPACE:
if (stack.size() > 1) {
popPage();
} else {
close();
}
backOrClose();
break;
default:
break;
}
// Després de qualsevol acció, si el cursor quedara sobre un ítem ocult
// (possible si una acció ha canviat la visibilitat pròpia de l'ítem actual,
// edge case defensiu), salta al següent visible.
}
void handleKey(SDL_Scancode sc) {
if (!isOpen()) {
return;
}
Page& page = stack.back();
if (page.items.empty()) {
if (sc == SDL_SCANCODE_BACKSPACE) {
backOrClose();
}
return;
}
if (!isVisible(page.items[page.cursor])) {
page.cursor = nextVisibleCursor(page, page.cursor, +1);
}
applyKeyToItem(page, sc);
// Defensa: si una acció ha amagat l'ítem que tenim sota el cursor,
// saltem al pròxim visible.
if (!stack.empty()) {
Page& top = stack.back();
if (!top.items.empty() && !isVisible(top.items[top.cursor])) {
@@ -522,6 +526,9 @@ namespace Menu {
}
}
// Forward decl: renderOneItem viu sota renderPageContent però aquest l'ha de cridar.
static void renderOneItem(Uint32* pixel_data, const Item& item, bool selected, int box_x, int y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max);
// Dibuixa el contingut d'una pàgina amb un offset horitzontal i un rang de clip.
// box_x/box_y són les coordenades de la caixa (per calcular posicions relatives);
// clip_x_min/clip_x_max limiten on es dibuixa text i la línia separadora.
@@ -560,69 +567,88 @@ namespace Menu {
return;
}
int y_slot = 0; // índex de fila visible (independent de l'índex real de l'ítem)
int y_slot = 0;
for (size_t i = 0; i < page.items.size(); i++) {
const Item& item = page.items[i];
if (!isVisible(item)) {
continue;
}
int y = items_y + (y_slot * ITEM_SPACING);
const int Y = items_y + (y_slot * ITEM_SPACING);
++y_slot;
bool selected = (static_cast<int>(i) == page.cursor);
Uint32 label_color = selected ? CURSOR_COLOR : LABEL_COLOR;
// ACTION: sense valor a la dreta — centrem el label amb el cursor just a l'esquerra.
if (item.kind == ItemKind::ACTION) {
int lw = font->width(item.label);
int lx = box_x + ((BOX_W - lw) / 2) + x_offset;
if (selected) {
font->drawClipped(pixel_data, lx - font->width("> "), y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
font->drawClipped(pixel_data, lx, y, item.label, label_color, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
continue;
}
if (selected) {
font->drawClipped(pixel_data, box_x + 4 + x_offset, y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
font->drawClipped(pixel_data, box_x + ITEM_PAD_X + x_offset, y, item.label, label_color, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
if (item.kind == ItemKind::SUBMENU) {
const char* arrow = ">>";
int aw = font->width(arrow);
Uint32 ac = selected ? CURSOR_COLOR : VALUE_COLOR;
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - aw + x_offset, y, arrow, ac, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
} else if (item.kind == ItemKind::KEY_BIND) {
bool this_capturing = (capturing == item.scancode);
const char* text = nullptr;
if (this_capturing) {
text = Locale::get("menu.values.press_key");
} else if (item.scancode != nullptr) {
text = SDL_GetScancodeName(*item.scancode);
} else {
text = "";
}
if ((text == nullptr) || (*text == 0)) {
text = Locale::get("menu.values.unknown");
}
int tw = font->width(text);
Uint32 tc = 0;
if (this_capturing) {
tc = 0xFF00FFFF;
} else {
tc = selected ? CURSOR_COLOR : VALUE_COLOR;
}
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - tw + x_offset, y, text, tc, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
} else if (item.get_value) {
std::string value = item.get_value();
int value_w = font->width(value.c_str());
Uint32 value_color = selected ? CURSOR_COLOR : VALUE_COLOR;
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - value_w + x_offset, y, value.c_str(), value_color, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
const bool SELECTED = (static_cast<int>(i) == page.cursor);
renderOneItem(pixel_data, item, SELECTED, box_x, Y, x_offset, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
}
static auto keybindText(const Item& item, bool this_capturing) -> const char* {
const char* text = nullptr;
if (this_capturing) {
text = Locale::get("menu.values.press_key");
} else if (item.scancode != nullptr) {
text = SDL_GetScancodeName(*item.scancode);
} else {
text = "";
}
if ((text == nullptr) || (*text == 0)) {
text = Locale::get("menu.values.unknown");
}
return text;
}
static void renderActionItem(Uint32* pixel_data, const Item& item, bool selected, int box_x, int y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) {
const Uint32 LABEL_COLOR_LOCAL = selected ? CURSOR_COLOR : LABEL_COLOR;
const int LW = font->width(item.label);
const int LX = box_x + ((BOX_W - LW) / 2) + x_offset;
if (selected) {
font->drawClipped(pixel_data, LX - font->width("> "), y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
font->drawClipped(pixel_data, LX, y, item.label, LABEL_COLOR_LOCAL, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
static auto keybindColor(bool this_capturing, bool selected) -> Uint32 {
if (this_capturing) {
return 0xFF00FFFF;
}
return selected ? CURSOR_COLOR : VALUE_COLOR;
}
static void renderItemValue(Uint32* pixel_data, const Item& item, bool selected, int box_x, int y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) {
if (item.kind == ItemKind::SUBMENU) {
const char* arrow = ">>";
const int AW = font->width(arrow);
const Uint32 AC = selected ? CURSOR_COLOR : VALUE_COLOR;
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - AW + x_offset, y, arrow, AC, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
return;
}
if (item.kind == ItemKind::KEY_BIND) {
const bool THIS_CAPTURING = (capturing == item.scancode);
const char* text = keybindText(item, THIS_CAPTURING);
const int TW = font->width(text);
const Uint32 TC = keybindColor(THIS_CAPTURING, selected);
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - TW + x_offset, y, text, TC, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
return;
}
if (item.get_value) {
const std::string VALUE = item.get_value();
const int VALUE_W = font->width(VALUE.c_str());
const Uint32 VALUE_COLOR_LOCAL = selected ? CURSOR_COLOR : VALUE_COLOR;
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - VALUE_W + x_offset, y, VALUE.c_str(), VALUE_COLOR_LOCAL, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
}
static void renderOneItem(Uint32* pixel_data, const Item& item, bool selected, int box_x, int y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) {
if (item.kind == ItemKind::ACTION) {
renderActionItem(pixel_data, item, selected, box_x, y, x_offset, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
return;
}
const Uint32 LABEL_COLOR_LOCAL = selected ? CURSOR_COLOR : LABEL_COLOR;
if (selected) {
font->drawClipped(pixel_data, box_x + 4 + x_offset, y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
font->drawClipped(pixel_data, box_x + ITEM_PAD_X + x_offset, y, item.label, LABEL_COLOR_LOCAL, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
renderItemValue(pixel_data, item, selected, box_x, y, x_offset, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
}
void render(Uint32* pixel_data) {
if (!isVisible() || !font || (pixel_data == nullptr)) {
return;