feat(ui): captura F9, tema a data/ui.yaml i retocs estètics
Captura de pantalla amb F9: volca el canvas ARGB a screenshots/scr_<timestamp>.png via stb_image_write. Tot el tema d'UI (colors, timings, fonts, flag hud.bg_enabled) passa a data/ui.yaml via UiConfig; els valors de Defaults::UI queden com a fallbacks si el fitxer falta o li manca alguna clau. Retocs estètics: paleta "uzebox" per a la UI, strings del HUD (P1/P2/V/T/PIRAMIDE/SALA) traduïts a locale, i fons del marcador opcional amb backdrop continu de franja per evitar el trencament visual durant el shake o l'animació d'entrada dels panells.
This commit is contained in:
1 parent
82b229222f
commit
dc8a7723e3
26 files changed
+2593
-210
No files matched your search
@@ -0,0 +1,139 @@
|
||||
#include "core/ui/ui_config.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "utils/defaults.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);
|
||||
}
|
||||
|
||||
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),
|
||||
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),
|
||||
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},
|
||||
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_;
|
||||
}
|
||||
|
||||
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_);
|
||||
}
|
||||
|
||||
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, "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_);
|
||||
}
|
||||
|
||||
std::cout << "UiConfig: carregat des de " << file_path << '\n';
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "UiConfig: error parsejant " << file_path << ": " << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// Configuració del tema UI (índexs de paleta, timings, fonts i flags)
|
||||
// carregada des de data/ui.yaml. Cada camp s'inicialitza amb el valor
|
||||
// de Defaults::UI com a fallback: si el fitxer falta o li manca una
|
||||
// clau, el joc s'executa amb el tema compilat.
|
||||
//
|
||||
// Patró idèntic a Locale i KeyConfig: Meyers singleton + load(path).
|
||||
// No hi ha hot-reload — els canvis al YAML requereixen reiniciar.
|
||||
class UiConfig {
|
||||
public:
|
||||
struct Font {
|
||||
std::string name;
|
||||
std::string gif;
|
||||
std::string fnt;
|
||||
int kerning;
|
||||
};
|
||||
|
||||
static auto get() -> UiConfig&;
|
||||
|
||||
UiConfig(const UiConfig&) = delete;
|
||||
UiConfig(UiConfig&&) = delete;
|
||||
auto operator=(const UiConfig&) -> UiConfig& = delete;
|
||||
auto operator=(UiConfig&&) -> UiConfig& = delete;
|
||||
|
||||
void load(const std::string& file_path);
|
||||
|
||||
// Colors — índexs sobre la paleta "ui" (Resource::Cache).
|
||||
[[nodiscard]] auto renderInfoText() const -> Uint8 { return render_info_text_; }
|
||||
[[nodiscard]] auto renderInfoShadow() const -> Uint8 { return render_info_shadow_; }
|
||||
[[nodiscard]] auto canvasBg() const -> Uint8 { return canvas_bg_; }
|
||||
[[nodiscard]] auto notificationText() const -> Uint8 { return notification_text_; }
|
||||
[[nodiscard]] auto notificationBg() const -> Uint8 { return notification_bg_; }
|
||||
[[nodiscard]] auto exitText() const -> Uint8 { return exit_text_; }
|
||||
[[nodiscard]] auto exitBg() const -> Uint8 { return exit_bg_; }
|
||||
[[nodiscard]] auto menuBg() const -> Uint8 { return menu_bg_; }
|
||||
[[nodiscard]] auto menuBorder() const -> Uint8 { return menu_border_; }
|
||||
[[nodiscard]] auto menuTitle() const -> Uint8 { return menu_title_; }
|
||||
[[nodiscard]] auto menuLabel() const -> Uint8 { return menu_label_; }
|
||||
[[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 hudBg() const -> Uint8 { return hud_bg_; }
|
||||
[[nodiscard]] auto hudLabel() const -> Uint8 { return hud_label_; }
|
||||
[[nodiscard]] auto hudValue() const -> Uint8 { return hud_value_; }
|
||||
[[nodiscard]] auto hudFlash() const -> Uint8 { return hud_flash_; }
|
||||
|
||||
// Timings (ms).
|
||||
[[nodiscard]] auto notificationMs() const -> int { return notification_ms_; }
|
||||
[[nodiscard]] auto exitMs() const -> int { return exit_ms_; }
|
||||
[[nodiscard]] auto hudEntryMs() const -> int { return hud_entry_ms_; }
|
||||
[[nodiscard]] auto hudFlashMs() const -> int { return hud_flash_ms_; }
|
||||
[[nodiscard]] auto hudShakeMs() const -> int { return hud_shake_ms_; }
|
||||
|
||||
// Fonts de UI.
|
||||
[[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; }
|
||||
[[nodiscard]] auto placeholderFont() const -> const Font& { return placeholder_font_; }
|
||||
[[nodiscard]] auto menuTitleFont() const -> const Font& { return menu_title_font_; }
|
||||
[[nodiscard]] auto menuItemsFont() const -> const Font& { return menu_items_font_; }
|
||||
|
||||
// Flags visuals.
|
||||
[[nodiscard]] auto hudBgEnabled() const -> bool { return hud_bg_enabled_; }
|
||||
|
||||
private:
|
||||
UiConfig();
|
||||
~UiConfig() = default;
|
||||
|
||||
Uint8 render_info_text_;
|
||||
Uint8 render_info_shadow_;
|
||||
Uint8 canvas_bg_;
|
||||
Uint8 notification_text_;
|
||||
Uint8 notification_bg_;
|
||||
Uint8 exit_text_;
|
||||
Uint8 exit_bg_;
|
||||
Uint8 menu_bg_;
|
||||
Uint8 menu_border_;
|
||||
Uint8 menu_title_;
|
||||
Uint8 menu_label_;
|
||||
Uint8 menu_value_;
|
||||
Uint8 menu_cursor_;
|
||||
Uint8 menu_disabled_;
|
||||
Uint8 hud_bg_;
|
||||
Uint8 hud_label_;
|
||||
Uint8 hud_value_;
|
||||
Uint8 hud_flash_;
|
||||
|
||||
int notification_ms_;
|
||||
int exit_ms_;
|
||||
int hud_entry_ms_;
|
||||
int hud_flash_ms_;
|
||||
int hud_shake_ms_;
|
||||
|
||||
Font hud_font_;
|
||||
Font placeholder_font_;
|
||||
Font menu_title_font_;
|
||||
Font menu_items_font_;
|
||||
|
||||
bool hud_bg_enabled_{false};
|
||||
};
|
||||
Reference in new issue
Block a user