Acaba la migració dels std::cout/cerr a Log::*:
- core/locale, core/ui/ui_config, core/rendering/{screen,text,gif,
surface, sprite/animated_sprite, sdl3gpu/*}.
- game/{options, debug_config, editor/editor_config, levels/level_manager,
map/{map,tilemap,paint_layer}, entities/entity_manager_loadlevel}.
Severitats coherents: warns per a configs absents amb fallback,
errors per a YAML malformat o E/S fallida, infos per a estats.
Resultat:
- 0 occurrences de std::cout/std::cerr a source/.
- Cap #include <iostream> orfà (eliminat de surface.cpp).
- Output funcional idèntic al històric; AEE_LOG filtra (ex.
`AEE_LOG=warn` només mostra warnings, `AEE_LOG=*:warn,Tag:info`
combina globals i overrides).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
353 lines
18 KiB
C++
353 lines
18 KiB
C++
#include "core/ui/ui_config.hpp"
|
|
|
|
#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"
|
|
#include "utils/log.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) {
|
|
Log::warn("UiConfig", "índex '{}'={} fora de rang 0..255 — ignorat", key, VALUE);
|
|
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)),
|
|
title_duration_ms_(Defaults::UI::Title::DURATION_MS),
|
|
title_bg_color_(Defaults::UI::Title::BG_COLOR),
|
|
title_text_color_(Defaults::UI::Title::TEXT_COLOR),
|
|
title_copyright_color_(Defaults::UI::Title::COPYRIGHT_COLOR),
|
|
title_press_start_outline_color_(Defaults::UI::Title::PRESS_START_OUTLINE_COLOR),
|
|
title_logo_y_percent_(Defaults::UI::Title::LOGO_Y_PERCENT),
|
|
title_prompt_y_percent_(Defaults::UI::Title::PROMPT_Y_PERCENT),
|
|
title_copyright_y_percent_(Defaults::UI::Title::COPYRIGHT_Y_PERCENT),
|
|
title_mini_logo_gap_px_(Defaults::UI::Title::MINI_LOGO_GAP_PX),
|
|
title_blink_visible_ms_(Defaults::UI::Title::BLINK_VISIBLE_MS),
|
|
title_blink_hidden_ms_(Defaults::UI::Title::BLINK_HIDDEN_MS),
|
|
title_starting_ms_(Defaults::UI::Title::STARTING_MS),
|
|
title_starting_blink_period_ms_(Defaults::UI::Title::STARTING_BLINK_PERIOD_MS),
|
|
title_fade_out_ms_(Defaults::UI::Title::FADE_OUT_MS),
|
|
title_fade_out_max_block_(Defaults::UI::Title::FADE_OUT_MAX_BLOCK),
|
|
title_music_fade_out_ms_(Defaults::UI::Title::MUSIC_FADE_OUT_MS),
|
|
title_attract_fade_to_black_ms_(Defaults::UI::Title::ATTRACT_FADE_TO_BLACK_MS),
|
|
title_intro_{
|
|
.fade_in_ms = Defaults::UI::Title::Intro::FADE_IN_MS,
|
|
.game_logo_at_ms = Defaults::UI::Title::Intro::GAME_LOGO_AT_MS,
|
|
.mini_logo_at_ms = Defaults::UI::Title::Intro::MINI_LOGO_AT_MS,
|
|
.mini_logo_slide_ms = Defaults::UI::Title::Intro::MINI_LOGO_SLIDE_MS,
|
|
.copyright_at_ms = Defaults::UI::Title::Intro::COPYRIGHT_AT_MS,
|
|
.copyright_slide_ms = Defaults::UI::Title::Intro::COPYRIGHT_SLIDE_MS,
|
|
.slide_curve = curveByName(Defaults::UI::Title::Intro::SLIDE_CURVE),
|
|
.press_start_at_ms = Defaults::UI::Title::Intro::PRESS_START_AT_MS,
|
|
},
|
|
title_heat_haze_{
|
|
.enabled = Defaults::UI::Title::HeatHaze::ENABLED,
|
|
.amplitude_px = Defaults::UI::Title::HeatHaze::AMPLITUDE_PX,
|
|
.wavelength_px = Defaults::UI::Title::HeatHaze::WAVELENGTH_PX,
|
|
.speed_hz = Defaults::UI::Title::HeatHaze::SPEED_HZ,
|
|
},
|
|
logo_scan_{
|
|
.duration_ms = Defaults::UI::Logo::Scan::DURATION_MS,
|
|
.tail_glitch_ms = Defaults::UI::Logo::Scan::TAIL_GLITCH_MS,
|
|
.band_height_px = Defaults::UI::Logo::Scan::BAND_HEIGHT_PX,
|
|
.band_alpha = Defaults::UI::Logo::Scan::BAND_ALPHA,
|
|
.band_pulse_hz = Defaults::UI::Logo::Scan::BAND_PULSE_HZ,
|
|
.band_pulse_depth = Defaults::UI::Logo::Scan::BAND_PULSE_DEPTH,
|
|
.glitch_amplitude_px = Defaults::UI::Logo::Scan::GLITCH_AMPLITUDE_PX,
|
|
.glitch_band_radius_px = Defaults::UI::Logo::Scan::GLITCH_BAND_RADIUS_PX,
|
|
.vhs_burst_per_sec = Defaults::UI::Logo::Scan::VHS_BURST_PER_SEC,
|
|
.vhs_burst_duration_ms = Defaults::UI::Logo::Scan::VHS_BURST_DURATION_MS,
|
|
.vhs_burst_height_px = Defaults::UI::Logo::Scan::VHS_BURST_HEIGHT_PX,
|
|
.vhs_burst_amplitude_px = Defaults::UI::Logo::Scan::VHS_BURST_AMPLITUDE_PX,
|
|
.flash_ms = Defaults::UI::Logo::Scan::FLASH_MS,
|
|
},
|
|
logo_timeline_{
|
|
.black_intro_ms = Defaults::UI::Logo::Timeline::BLACK_INTRO_MS,
|
|
.hold_silent_ms = Defaults::UI::Logo::Timeline::HOLD_SILENT_MS,
|
|
.hold_with_music_ms = Defaults::UI::Logo::Timeline::HOLD_WITH_MUSIC_MS,
|
|
.fade_out_ms = Defaults::UI::Logo::Timeline::FADE_OUT_MS,
|
|
.black_outro_ms = Defaults::UI::Logo::Timeline::BLACK_OUTRO_MS,
|
|
},
|
|
menu_select_sound_(Defaults::UI::MENU_SELECT_SOUND),
|
|
menu_accept_sound_(Defaults::UI::MENU_ACCEPT_SOUND),
|
|
player_start_sound_(Defaults::UI::PLAYER_START_SOUND),
|
|
logo_glitch_sound_(Defaults::UI::LOGO_GLITCH_SOUND),
|
|
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},
|
|
copyright_font_{.name = Defaults::UI::Font::COPYRIGHT_NAME, .gif = Defaults::UI::Font::COPYRIGHT_GIF, .fnt = Defaults::UI::Font::COPYRIGHT_FNT, .kerning = Defaults::UI::Font::COPYRIGHT_KERNING},
|
|
notifier_{
|
|
.padding_h = Defaults::UI::Notifier::PADDING_H,
|
|
.padding_top = Defaults::UI::Notifier::PADDING_TOP,
|
|
.padding_bottom = Defaults::UI::Notifier::PADDING_BOTTOM,
|
|
.line_gap = Defaults::UI::Notifier::LINE_GAP,
|
|
.margin_top = Defaults::UI::Notifier::MARGIN_TOP,
|
|
.border_px = Defaults::UI::Notifier::BORDER_PX,
|
|
.slide_ms = Defaults::UI::Notifier::SLIDE_MS,
|
|
.slide_in_curve = curveByName(Defaults::UI::Notifier::SLIDE_IN_CURVE),
|
|
.slide_out_curve = curveByName(Defaults::UI::Notifier::SLIDE_OUT_CURVE),
|
|
} {
|
|
}
|
|
|
|
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()) {
|
|
Log::warn("UiConfig", "corba '{}' desconeguda — s'usa linear", name);
|
|
return &Easing::linear;
|
|
}
|
|
return IT->second;
|
|
}
|
|
|
|
void UiConfig::load(const std::string& file_path) {
|
|
auto bytes = Resource::Helper::loadFile(file_path);
|
|
if (bytes.empty()) {
|
|
Log::warn("UiConfig", "no s'ha pogut obrir {} — s'usaran els defaults compilats", file_path);
|
|
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_);
|
|
readFont(f, "copyright", copyright_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_);
|
|
}
|
|
|
|
if (yaml.contains("sounds")) {
|
|
const auto& s = yaml["sounds"];
|
|
readField(s, "menu_select", menu_select_sound_);
|
|
readField(s, "menu_accept", menu_accept_sound_);
|
|
readField(s, "player_start", player_start_sound_);
|
|
readField(s, "logo_glitch", logo_glitch_sound_);
|
|
}
|
|
|
|
if (yaml.contains("title")) {
|
|
const auto& t = yaml["title"];
|
|
readField(t, "duration_ms", title_duration_ms_);
|
|
readColorIndex(t, "bg_color", title_bg_color_);
|
|
readColorIndex(t, "text_color", title_text_color_);
|
|
readColorIndex(t, "copyright_color", title_copyright_color_);
|
|
readColorIndex(t, "press_start_outline_color", title_press_start_outline_color_);
|
|
readField(t, "logo_y_percent", title_logo_y_percent_);
|
|
readField(t, "prompt_y_percent", title_prompt_y_percent_);
|
|
readField(t, "copyright_y_percent", title_copyright_y_percent_);
|
|
readField(t, "mini_logo_gap_px", title_mini_logo_gap_px_);
|
|
readField(t, "blink_visible_ms", title_blink_visible_ms_);
|
|
readField(t, "blink_hidden_ms", title_blink_hidden_ms_);
|
|
readField(t, "starting_ms", title_starting_ms_);
|
|
readField(t, "starting_blink_period_ms", title_starting_blink_period_ms_);
|
|
readField(t, "fade_out_ms", title_fade_out_ms_);
|
|
readField(t, "fade_out_max_block", title_fade_out_max_block_);
|
|
readField(t, "music_fade_out_ms", title_music_fade_out_ms_);
|
|
readField(t, "attract_fade_to_black_ms", title_attract_fade_to_black_ms_);
|
|
if (t.contains("intro")) {
|
|
const auto& in = t["intro"];
|
|
readField(in, "fade_in_ms", title_intro_.fade_in_ms);
|
|
readField(in, "game_logo_at_ms", title_intro_.game_logo_at_ms);
|
|
readField(in, "mini_logo_at_ms", title_intro_.mini_logo_at_ms);
|
|
readField(in, "mini_logo_slide_ms", title_intro_.mini_logo_slide_ms);
|
|
readField(in, "copyright_at_ms", title_intro_.copyright_at_ms);
|
|
readField(in, "copyright_slide_ms", title_intro_.copyright_slide_ms);
|
|
readCurve(in, "slide_curve", title_intro_.slide_curve);
|
|
readField(in, "press_start_at_ms", title_intro_.press_start_at_ms);
|
|
}
|
|
if (t.contains("heat_haze")) {
|
|
const auto& hh = t["heat_haze"];
|
|
readField(hh, "enabled", title_heat_haze_.enabled);
|
|
readField(hh, "amplitude_px", title_heat_haze_.amplitude_px);
|
|
readField(hh, "wavelength_px", title_heat_haze_.wavelength_px);
|
|
readField(hh, "speed_hz", title_heat_haze_.speed_hz);
|
|
}
|
|
}
|
|
|
|
if (yaml.contains("logo")) {
|
|
const auto& l = yaml["logo"];
|
|
if (l.contains("scan")) {
|
|
const auto& s = l["scan"];
|
|
readField(s, "duration_ms", logo_scan_.duration_ms);
|
|
readField(s, "tail_glitch_ms", logo_scan_.tail_glitch_ms);
|
|
readField(s, "band_height_px", logo_scan_.band_height_px);
|
|
readField(s, "band_alpha", logo_scan_.band_alpha);
|
|
readField(s, "band_pulse_hz", logo_scan_.band_pulse_hz);
|
|
readField(s, "band_pulse_depth", logo_scan_.band_pulse_depth);
|
|
readField(s, "glitch_amplitude_px", logo_scan_.glitch_amplitude_px);
|
|
readField(s, "glitch_band_radius_px", logo_scan_.glitch_band_radius_px);
|
|
readField(s, "vhs_burst_per_sec", logo_scan_.vhs_burst_per_sec);
|
|
readField(s, "vhs_burst_duration_ms", logo_scan_.vhs_burst_duration_ms);
|
|
readField(s, "vhs_burst_height_px", logo_scan_.vhs_burst_height_px);
|
|
readField(s, "vhs_burst_amplitude_px", logo_scan_.vhs_burst_amplitude_px);
|
|
readField(s, "flash_ms", logo_scan_.flash_ms);
|
|
}
|
|
if (l.contains("timeline")) {
|
|
const auto& tl = l["timeline"];
|
|
readField(tl, "black_intro_ms", logo_timeline_.black_intro_ms);
|
|
readField(tl, "hold_silent_ms", logo_timeline_.hold_silent_ms);
|
|
readField(tl, "hold_with_music_ms", logo_timeline_.hold_with_music_ms);
|
|
readField(tl, "fade_out_ms", logo_timeline_.fade_out_ms);
|
|
readField(tl, "black_outro_ms", logo_timeline_.black_outro_ms);
|
|
}
|
|
}
|
|
|
|
if (yaml.contains("notifier")) {
|
|
const auto& n = yaml["notifier"];
|
|
readField(n, "padding_h", notifier_.padding_h);
|
|
readField(n, "padding_top", notifier_.padding_top);
|
|
readField(n, "padding_bottom", notifier_.padding_bottom);
|
|
readField(n, "line_gap", notifier_.line_gap);
|
|
readField(n, "margin_top", notifier_.margin_top);
|
|
readField(n, "border_px", notifier_.border_px);
|
|
readField(n, "slide_ms", notifier_.slide_ms);
|
|
readCurve(n, "slide_in_curve", notifier_.slide_in_curve);
|
|
readCurve(n, "slide_out_curve", notifier_.slide_out_curve);
|
|
}
|
|
|
|
Log::info("UiConfig", "carregat des de {}", file_path);
|
|
} catch (const std::exception& e) {
|
|
Log::error("UiConfig", "error parsejant {}: {}", file_path, e.what());
|
|
}
|
|
}
|