Files
aee-arcade/source/core/ui/ui_config.cpp
T
JailDesigner 3b05704e97 feat(intro): colors del rètol independents + restitueix line_gap_px
Dues claus noves a data/ui.yaml > colors:
  room_text         (relleno, default 232)
  room_text_outline (contorn, default 255)
Abans el relleno reciclava notification_text (compartit amb les
notificacions del HUD) i el contorn estava hardcodejat a 255 dins
renderRoomText. Ara són canals separats: tocar el rètol no afecta
les notificacions i tot viu al YAML.

UiConfig guanya roomText() / roomTextOutline(); Defaults::UI hi posa
els fallbacks; renderRoomText els consumeix.

Drive-by: torna a posar la clau room_intro.line_gap_px: 8 al
data/scenes/game.yaml — s'havia perdut en una edició manual del
fitxer per afinar timings.
2026-04-26 17:02:26 +02:00

193 lines
8.5 KiB
C++

#include "core/ui/ui_config.hpp"
#include <iostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/defaults.hpp"
#include "utils/easing.hpp"
namespace {
// Lector de camp amb fallback: si la clau existeix al node, escriu a
// `dst` el valor convertit; si no, deixa `dst` com estava (que al
// constructor ja hem inicialitzat amb Defaults::UI).
template <typename T>
void readField(const fkyaml::node& node, const char* key, T& dst) {
if (node.contains(key)) { dst = node[key].get_value<T>(); }
}
// Variant per a Uint8 — fkyaml no serialitza a Uint8 directament, cal
// passar per int i encaixar al rang 0..255 (valors fora de rang es
// trunquen i s'avisen).
void readColorIndex(const fkyaml::node& node, const char* key, Uint8& dst) {
if (!node.contains(key)) { return; }
const int VALUE = node[key].get_value<int>();
if (VALUE < 0 || VALUE > 255) {
std::cerr << "UiConfig: índex '" << key << "'=" << VALUE << " fora de rang 0..255 — ignorat\n";
return;
}
dst = static_cast<Uint8>(VALUE);
}
// Registre de corbes disponibles. Afegir-ne una nova és només una
// entrada ací: la llegeix `UiConfig::curveByName` i queda exposada al
// YAML pel seu nom. No hi ha fallback mut: si el nom no hi és, l'api
// retorna linear i avisa per stderr perquè l'error no passi en silenci.
auto curveRegistry() -> const std::unordered_map<std::string_view, UiConfig::CurveFn>& {
static const std::unordered_map<std::string_view, UiConfig::CurveFn> REGISTRY = {
{"linear", &Easing::linear},
{"outQuad", &Easing::outQuad},
{"inQuad", &Easing::inQuad},
{"inOutQuad", &Easing::inOutQuad},
{"outCubic", &Easing::outCubic},
{"inCubic", &Easing::inCubic},
};
return REGISTRY;
}
void readCurve(const fkyaml::node& node, const char* key, UiConfig::CurveFn& dst) {
if (!node.contains(key)) { return; }
const auto NAME = node[key].get_value<std::string>();
dst = UiConfig::curveByName(NAME);
}
void readFont(const fkyaml::node& node, const char* key, UiConfig::Font& dst) {
if (!node.contains(key)) { return; }
const auto& f = node[key];
readField(f, "name", dst.name);
readField(f, "gif", dst.gif);
readField(f, "fnt", dst.fnt);
readField(f, "kerning", dst.kerning);
}
} // namespace
UiConfig::UiConfig()
: render_info_text_(Defaults::UI::RENDER_INFO_TEXT),
render_info_shadow_(Defaults::UI::RENDER_INFO_SHADOW),
canvas_bg_(Defaults::UI::CANVAS_BG),
notification_text_(Defaults::UI::NOTIFICATION_TEXT),
notification_bg_(Defaults::UI::NOTIFICATION_BG),
exit_text_(Defaults::UI::EXIT_TEXT),
exit_bg_(Defaults::UI::EXIT_BG),
menu_bg_(Defaults::UI::MENU_BG),
menu_border_(Defaults::UI::MENU_BORDER),
menu_title_(Defaults::UI::MENU_TITLE),
menu_label_(Defaults::UI::MENU_LABEL),
menu_value_(Defaults::UI::MENU_VALUE),
menu_cursor_(Defaults::UI::MENU_CURSOR),
menu_disabled_(Defaults::UI::MENU_DISABLED),
hud_bg_(Defaults::UI::HUD_BG),
hud_label_(Defaults::UI::HUD_LABEL),
hud_value_(Defaults::UI::HUD_VALUE),
hud_flash_(Defaults::UI::HUD_FLASH),
room_text_(Defaults::UI::ROOM_TEXT),
room_text_outline_(Defaults::UI::ROOM_TEXT_OUTLINE),
notification_ms_(Defaults::UI::NOTIFICATION_TIME_MS),
exit_ms_(Defaults::UI::EXIT_TIME_MS),
hud_entry_ms_(Defaults::UI::HUD_ENTRY_MS),
hud_flash_ms_(Defaults::UI::HUD_FLASH_MS),
hud_shake_ms_(Defaults::UI::HUD_SHAKE_MS),
pause_enter_ms_(Defaults::UI::PAUSE_ENTER_MS),
pause_exit_ms_(Defaults::UI::PAUSE_EXIT_MS),
pause_enter_curve_(curveByName(Defaults::UI::PAUSE_ENTER_CURVE)),
pause_exit_curve_(curveByName(Defaults::UI::PAUSE_EXIT_CURVE)),
hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING},
placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING},
room_text_font_{.name = Defaults::UI::Font::ROOM_TEXT_NAME, .gif = Defaults::UI::Font::ROOM_TEXT_GIF, .fnt = Defaults::UI::Font::ROOM_TEXT_FNT, .kerning = Defaults::UI::Font::ROOM_TEXT_KERNING},
menu_title_font_{.name = Defaults::UI::Font::MENU_TITLE_NAME, .gif = Defaults::UI::Font::MENU_TITLE_GIF, .fnt = Defaults::UI::Font::MENU_TITLE_FNT, .kerning = Defaults::UI::Font::MENU_TITLE_KERNING},
menu_items_font_{.name = Defaults::UI::Font::MENU_ITEMS_NAME, .gif = Defaults::UI::Font::MENU_ITEMS_GIF, .fnt = Defaults::UI::Font::MENU_ITEMS_FNT, .kerning = Defaults::UI::Font::MENU_ITEMS_KERNING} {
}
auto UiConfig::get() -> UiConfig& {
static UiConfig instance_;
return instance_;
}
auto UiConfig::curveByName(const std::string& name) -> CurveFn {
const auto& reg = curveRegistry();
const auto IT = reg.find(name);
if (IT == reg.end()) {
std::cerr << "UiConfig: corba '" << name << "' desconeguda — s'usa linear\n";
return &Easing::linear;
}
return IT->second;
}
void UiConfig::load(const std::string& file_path) {
auto bytes = Resource::Helper::loadFile(file_path);
if (bytes.empty()) {
std::cerr << "UiConfig: no s'ha pogut obrir " << file_path << " — s'usaran els defaults compilats\n";
return;
}
try {
const auto* begin = reinterpret_cast<const char*>(bytes.data());
const auto* end = begin + bytes.size();
auto yaml = fkyaml::node::deserialize(begin, end);
if (yaml.contains("colors")) {
const auto& c = yaml["colors"];
readColorIndex(c, "render_info_text", render_info_text_);
readColorIndex(c, "render_info_shadow", render_info_shadow_);
readColorIndex(c, "canvas_bg", canvas_bg_);
readColorIndex(c, "notification_text", notification_text_);
readColorIndex(c, "notification_bg", notification_bg_);
readColorIndex(c, "exit_text", exit_text_);
readColorIndex(c, "exit_bg", exit_bg_);
readColorIndex(c, "menu_bg", menu_bg_);
readColorIndex(c, "menu_border", menu_border_);
readColorIndex(c, "menu_title", menu_title_);
readColorIndex(c, "menu_label", menu_label_);
readColorIndex(c, "menu_value", menu_value_);
readColorIndex(c, "menu_cursor", menu_cursor_);
readColorIndex(c, "menu_disabled", menu_disabled_);
readColorIndex(c, "hud_bg", hud_bg_);
readColorIndex(c, "hud_label", hud_label_);
readColorIndex(c, "hud_value", hud_value_);
readColorIndex(c, "hud_flash", hud_flash_);
readColorIndex(c, "room_text", room_text_);
readColorIndex(c, "room_text_outline", room_text_outline_);
}
if (yaml.contains("timings")) {
const auto& t = yaml["timings"];
readField(t, "notification_ms", notification_ms_);
readField(t, "exit_ms", exit_ms_);
}
if (yaml.contains("fonts")) {
const auto& f = yaml["fonts"];
readFont(f, "hud", hud_font_);
readFont(f, "placeholder", placeholder_font_);
readFont(f, "room_text", room_text_font_);
readFont(f, "menu_title", menu_title_font_);
readFont(f, "menu_items", menu_items_font_);
}
if (yaml.contains("hud")) {
const auto& h = yaml["hud"];
readField(h, "bg_enabled", hud_bg_enabled_);
readField(h, "entry_ms", hud_entry_ms_);
readField(h, "flash_ms", hud_flash_ms_);
readField(h, "shake_ms", hud_shake_ms_);
}
if (yaml.contains("pause")) {
const auto& p = yaml["pause"];
readField(p, "enter_ms", pause_enter_ms_);
readField(p, "exit_ms", pause_exit_ms_);
readCurve(p, "enter_curve", pause_enter_curve_);
readCurve(p, "exit_curve", pause_exit_curve_);
}
std::cout << "UiConfig: carregat des de " << file_path << '\n';
} catch (const std::exception& e) {
std::cerr << "UiConfig: error parsejant " << file_path << ": " << e.what() << '\n';
}
}