feat(ui): outline per slot al text del menú

This commit is contained in:
JailDesigner committed 2026-05-14 10:11:50 +02:00
1 parent 0e8a195ddd
commit b6403e8de5
6 files changed
+89 -22

No files matched your search

+8
View File
@@ -38,9 +38,13 @@ menu_themes:
bg: 2 # marró-vermell fosc (#480000)
border: 232 # cian brillant (#00B6FF)
title: 237 # lavanda (#B6B6FF)
title_outline: 64 # navy fosc (#0000AA) — halo fred del títol
label: 58 # verd llima (#48FF00)
label_outline: 17 # verd molt fosc (#244800) — bord same-family del label
value: 232 # cian brillant (#00B6FF) — coincideix amb el border
value_outline: 64 # navy fosc (#0000AA) — halo cian
cursor: 71 # magenta-vermell (#FF0055)
cursor_outline: 1 # granat fosc (#240000) — halo càlid del cursor
disabled: 16 # verd fosc (#004800)
# Tema "egípci": família or/ambre/marró coherent amb el HUD i el
# rètol de transició de sala (room_text). El bg comparteix to amb
@@ -50,9 +54,13 @@ menu_themes:
bg: 10 # marró cremat (#482400)
border: 55 # or brillant (#FFDA00)
title: 127 # groc llimona viu (#FFFF55)
title_outline: 19 # oliva fosc (#6D4800) — halo càlid del groc
label: 47 # ambre (#FFB600)
label_outline: 11 # marró (#6D2400) — bord càlid del label
value: 191 # crema clar (#FFFFAA)
value_outline: 19 # oliva fosc (#6D4800) — recolza el crema
cursor: 23 # vermilló (#FF4800)
cursor_outline: 9 # marró molt fosc (#240000) — halo profund del cursor
disabled: 19 # oliva fosc (#6D4800)
timings:
+16 -8
View File
@@ -198,42 +198,50 @@ void DefineButtons::render() const {
// Títol: nom del mando.
const std::string TITLE = pad_name_;
title_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
title_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
CENTER_X,
cursor_y,
TITLE,
TITLE_KERNING,
UiConfig::get().menuTitle());
UiConfig::get().menuTitle(),
1,
UiConfig::get().menuTitleOutline());
cursor_y += TITLE_H + LINE_GAP;
if (message_shown_) {
// Pantalla final: missatge de configuració completa.
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
CENTER_X,
cursor_y,
Locale::get().text("menu.values.define_buttons_complete"),
ITEM_KERNING,
UiConfig::get().menuValue());
UiConfig::get().menuValue(),
1,
UiConfig::get().menuValueOutline());
return;
}
// Instrucció + acció pendent. Replica visualment el WindowMessage de
// coffee_crisis_arcade_edition.
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
CENTER_X,
cursor_y,
Locale::get().text("menu.values.press_button"),
ITEM_KERNING,
UiConfig::get().menuLabel());
UiConfig::get().menuLabel(),
1,
UiConfig::get().menuLabelOutline());
cursor_y += ITEM_H + LINE_GAP;
if (index_ < sequence_.size()) {
const auto& current = sequence_[index_];
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
CENTER_X,
cursor_y,
Locale::get().text(current.label_key),
ITEM_KERNING,
UiConfig::get().menuCursor());
UiConfig::get().menuCursor(),
1,
UiConfig::get().menuCursorOutline());
}
}
+8
View File
@@ -81,6 +81,10 @@ UiConfig::UiConfig()
menu_value_(Defaults::UI::MENU_VALUE),
menu_cursor_(Defaults::UI::MENU_CURSOR),
menu_disabled_(Defaults::UI::MENU_DISABLED),
menu_title_outline_(Defaults::UI::MENU_TITLE_OUTLINE),
menu_label_outline_(Defaults::UI::MENU_LABEL_OUTLINE),
menu_value_outline_(Defaults::UI::MENU_VALUE_OUTLINE),
menu_cursor_outline_(Defaults::UI::MENU_CURSOR_OUTLINE),
hud_bg_(Defaults::UI::HUD_BG),
hud_p1_label_(Defaults::UI::HUD_P1_LABEL),
hud_p1_value_(Defaults::UI::HUD_P1_VALUE),
@@ -238,6 +242,10 @@ void UiConfig::load(const std::string& file_path) {
readColorIndex(t, "value", menu_value_);
readColorIndex(t, "cursor", menu_cursor_);
readColorIndex(t, "disabled", menu_disabled_);
readColorIndex(t, "title_outline", menu_title_outline_);
readColorIndex(t, "label_outline", menu_label_outline_);
readColorIndex(t, "value_outline", menu_value_outline_);
readColorIndex(t, "cursor_outline", menu_cursor_outline_);
} else {
Log::warn("UiConfig", "menu_theme '{}' no existeix a menu_themes — s'usen defaults", THEME_NAME);
}
+8
View File
@@ -65,6 +65,10 @@ class UiConfig {
[[nodiscard]] auto menuValue() const -> Uint8 { return menu_value_; }
[[nodiscard]] auto menuCursor() const -> Uint8 { return menu_cursor_; }
[[nodiscard]] auto menuDisabled() const -> Uint8 { return menu_disabled_; }
[[nodiscard]] auto menuTitleOutline() const -> Uint8 { return menu_title_outline_; }
[[nodiscard]] auto menuLabelOutline() const -> Uint8 { return menu_label_outline_; }
[[nodiscard]] auto menuValueOutline() const -> Uint8 { return menu_value_outline_; }
[[nodiscard]] auto menuCursorOutline() const -> Uint8 { return menu_cursor_outline_; }
[[nodiscard]] auto hudBg() const -> Uint8 { return hud_bg_; }
// Famílies de color del HUD per bloc (LEFT/CENTER/RIGHT). Cada
// família té label (etiqueta apagada), value (valor destacat) i
@@ -214,6 +218,10 @@ class UiConfig {
Uint8 menu_value_;
Uint8 menu_cursor_;
Uint8 menu_disabled_;
Uint8 menu_title_outline_;
Uint8 menu_label_outline_;
Uint8 menu_value_outline_;
Uint8 menu_cursor_outline_;
Uint8 hud_bg_;
Uint8 hud_p1_label_;
Uint8 hud_p1_value_;
+41 -14
View File
@@ -996,23 +996,38 @@ namespace {
return out;
}
// Parell (fill, outline) per a un text del menú. Extret per a
// collir-los en un sol pas i no inflar la complexitat cognitiva
// de `renderPage` amb dos ternaris en paral·lel.
struct MenuColorPair {
Uint8 fill;
Uint8 outline;
};
auto pickValueColors(bool selected) -> MenuColorPair {
if (selected) { return {.fill = UiConfig::get().menuCursor(), .outline = UiConfig::get().menuCursorOutline()}; }
return {.fill = UiConfig::get().menuValue(), .outline = UiConfig::get().menuValueOutline()};
}
// Dibuixa el value d'un item a la dreta del box, truncant-lo amb
// "..." si no cap a l'esquerra de la label. Extret de `renderPage`
// per a mantenir la complexitat cognitiva sota el llindar de
// clang-tidy.
void drawItemValue(Text& font, int box_x, int box_w, int cursor_y, int cursor_width, const std::string& label, const std::string& value, int kerning, Uint8 color) {
void drawItemValue(Text& font, int box_x, int box_w, int cursor_y, int cursor_width, const std::string& label, const std::string& value, int kerning, Uint8 color, Uint8 outline) {
const int LABEL_W = font.length(label, kerning);
const int AVAILABLE_W = box_w - (2 * PAD_X) - cursor_width - LABEL_W - MIN_LABEL_VALUE_GAP;
if (AVAILABLE_W <= 0) { return; }
const std::string DISPLAY = truncateToFit(font, value, AVAILABLE_W, kerning);
if (DISPLAY.empty()) { return; }
const int VALUE_W = font.length(DISPLAY, kerning);
font.writeDX(Text::COLOR_FLAG,
font.writeDX(Text::COLOR_FLAG | Text::STROKE_FLAG,
box_x + box_w - PAD_X - VALUE_W,
cursor_y,
DISPLAY,
kerning,
color);
color,
1,
outline);
}
// Mandos visibles al cycle d'un jugador: tots els connectats
@@ -1305,21 +1320,25 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
const int CURSOR_WIDTH = items_font_->length("> ", ITEM_KERNING);
const int LABEL_X = box_x + PAD_X + CURSOR_WIDTH;
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
CENTER_X,
cursor_y,
Locale::get().text(page.title_key),
TITLE_KERNING,
UiConfig::get().menuTitle());
UiConfig::get().menuTitle(),
1,
UiConfig::get().menuTitleOutline());
cursor_y += TITLE_H + GAP_Y - TITLE_DESCENDER_TRIM;
if (!page.subtitle.empty()) {
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
CENTER_X,
cursor_y,
page.subtitle,
TITLE_KERNING,
UiConfig::get().menuTitle());
UiConfig::get().menuTitle(),
1,
UiConfig::get().menuTitleOutline());
cursor_y += TITLE_H + GAP_Y - TITLE_DESCENDER_TRIM;
}
@@ -1344,13 +1363,17 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
: formatValue(item);
Uint8 label_color = UiConfig::get().menuLabel();
Uint8 label_outline = UiConfig::get().menuLabelOutline();
if (item.kind == Item::Kind::LABEL) {
// Mateix to que el títol de la pàgina: les LABELs actuen com
// a subtítol-divisor (p.ex. "Jugador 1" a Teclat, "ver.X.Y" a
// Sistema) i han de tenir la mateixa presència visual.
// Sistema) i han de tenir la mateixa presència visual — fill
// i outline van aparellats.
label_color = UiConfig::get().menuTitle();
label_outline = UiConfig::get().menuTitleOutline();
} else if (SELECTED) {
label_color = UiConfig::get().menuCursor();
label_outline = UiConfig::get().menuCursorOutline();
}
int label_x = LABEL_X;
@@ -1362,27 +1385,31 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
}
if (SELECTED) {
items_font_->writeDX(Text::COLOR_FLAG,
items_font_->writeDX(Text::COLOR_FLAG | Text::STROKE_FLAG,
label_x - CURSOR_WIDTH,
cursor_y,
"> ",
ITEM_KERNING,
UiConfig::get().menuCursor());
UiConfig::get().menuCursor(),
1,
UiConfig::get().menuCursorOutline());
}
items_font_->writeDX(Text::COLOR_FLAG,
items_font_->writeDX(Text::COLOR_FLAG | Text::STROKE_FLAG,
label_x,
cursor_y,
LABEL_TEXT,
ITEM_KERNING,
label_color);
label_color,
1,
label_outline);
// Pàgines centrades no dibuixen columna de valor (s'assumeix que
// només tenen ACTION / LABEL); els Toggle/Cycle viuen en pàgines
// d'opcions amb layout esquerra-dreta.
if (!page.center_items && !VALUE.empty()) {
const Uint8 VALUE_COLOR = SELECTED ? UiConfig::get().menuCursor() : UiConfig::get().menuValue();
drawItemValue(*items_font_, box_x, box_w, cursor_y, CURSOR_WIDTH, LABEL_TEXT, VALUE, ITEM_KERNING, VALUE_COLOR);
const auto VALUE_COLORS = pickValueColors(SELECTED);
drawItemValue(*items_font_, box_x, box_w, cursor_y, CURSOR_WIDTH, LABEL_TEXT, VALUE, ITEM_KERNING, VALUE_COLORS.fill, VALUE_COLORS.outline);
}
cursor_y += ITEM_H;
+8
View File
@@ -253,6 +253,14 @@ namespace Defaults {
constexpr Uint8 MENU_VALUE = 232; // Valor actual.
constexpr Uint8 MENU_CURSOR = 71; // Item seleccionat.
constexpr Uint8 MENU_DISABLED = 16; // Label en gris quan no aplica.
// Outlines del text del menú (8 veïns). Un per slot perquè
// cada to de fill demana un halo diferent: el contorn no és
// un negre uniform sinó una versió fosca de la família del
// fill (efecte "embossing").
constexpr Uint8 MENU_TITLE_OUTLINE = 0;
constexpr Uint8 MENU_LABEL_OUTLINE = 0;
constexpr Uint8 MENU_VALUE_OUTLINE = 0;
constexpr Uint8 MENU_CURSOR_OUTLINE = 0;
// Índexs de paleta per al marcador de joc (HUD de la GameScene).
// Tres "famílies" de color — un parell (label, value) + sombra per