- afegida carpeta release

- jitter en renderInfo
This commit is contained in:
2026-04-05 09:39:05 +02:00
parent 3aa6078054
commit c0553c6d37
104 changed files with 78786 additions and 63 deletions

View File

@@ -120,43 +120,27 @@ namespace Menu {
if (dir < 0) Screen::get()->decZoom();
else if (dir > 0) Screen::get()->incZoom(); }, nullptr});
p.items.push_back({Locale::get("menu.items.screen"), ItemKind::Toggle,
[] { return std::string(Screen::get()->isFullscreen() ? Locale::get("menu.values.fullscreen") : Locale::get("menu.values.windowed")); },
[](int) { Screen::get()->toggleFullscreen(); }, nullptr});
p.items.push_back({Locale::get("menu.items.screen"), ItemKind::Toggle, [] { return std::string(Screen::get()->isFullscreen() ? Locale::get("menu.values.fullscreen") : Locale::get("menu.values.windowed")); }, [](int) { Screen::get()->toggleFullscreen(); }, nullptr});
p.items.push_back({Locale::get("menu.items.shader"), ItemKind::Toggle,
[] { return onOff(Options::video.shader_enabled); },
[](int) { Screen::get()->toggleShaders(); }, nullptr});
p.items.push_back({Locale::get("menu.items.shader"), ItemKind::Toggle, [] { return onOff(Options::video.shader_enabled); }, [](int) { Screen::get()->toggleShaders(); }, nullptr});
p.items.push_back({Locale::get("menu.items.aspect_4_3"), ItemKind::Toggle,
[] { return yesNo(Options::video.aspect_ratio_4_3); },
[](int) { Screen::get()->toggleAspectRatio(); }, nullptr});
p.items.push_back({Locale::get("menu.items.aspect_4_3"), ItemKind::Toggle, [] { return yesNo(Options::video.aspect_ratio_4_3); }, [](int) { Screen::get()->toggleAspectRatio(); }, nullptr});
p.items.push_back({Locale::get("menu.items.supersampling"), ItemKind::Toggle,
[] { return onOff(Options::video.supersampling); },
[](int) { Screen::get()->toggleSupersampling(); }, nullptr});
p.items.push_back({Locale::get("menu.items.supersampling"), ItemKind::Toggle, [] { return onOff(Options::video.supersampling); }, [](int) { Screen::get()->toggleSupersampling(); }, nullptr});
p.items.push_back({Locale::get("menu.items.vsync"), ItemKind::Toggle,
[] { return onOff(Options::video.vsync); },
[](int) { Screen::get()->toggleVSync(); }, nullptr});
p.items.push_back({Locale::get("menu.items.vsync"), ItemKind::Toggle, [] { return onOff(Options::video.vsync); }, [](int) { Screen::get()->toggleVSync(); }, nullptr});
p.items.push_back({Locale::get("menu.items.integer_scale"), ItemKind::Toggle,
[] { return onOff(Options::video.integer_scale); },
[](int) { Screen::get()->toggleIntegerScale(); }, nullptr});
p.items.push_back({Locale::get("menu.items.integer_scale"), ItemKind::Toggle, [] { return onOff(Options::video.integer_scale); }, [](int) { Screen::get()->toggleIntegerScale(); }, nullptr});
p.items.push_back({Locale::get("menu.items.shader_type"), ItemKind::Cycle,
[] { return std::string(Screen::get()->getActiveShaderName()); }, [](int dir) {
p.items.push_back({Locale::get("menu.items.shader_type"), ItemKind::Cycle, [] { return std::string(Screen::get()->getActiveShaderName()); }, [](int dir) {
if (dir < 0) Screen::get()->prevShaderType();
else Screen::get()->nextShaderType(); }, nullptr});
p.items.push_back({Locale::get("menu.items.preset"), ItemKind::Cycle,
[] { return std::string(Screen::get()->getCurrentPresetName()); }, [](int dir) {
p.items.push_back({Locale::get("menu.items.preset"), ItemKind::Cycle, [] { return std::string(Screen::get()->getCurrentPresetName()); }, [](int dir) {
if (dir < 0) Screen::get()->prevPreset();
else Screen::get()->nextPreset(); }, nullptr});
p.items.push_back({Locale::get("menu.items.stretch_filter"), ItemKind::Toggle,
[] { return std::string(Options::video.stretch_filter_linear ? Locale::get("menu.values.linear") : Locale::get("menu.values.nearest")); },
[](int) { Screen::get()->toggleStretchFilter(); }, nullptr});
p.items.push_back({Locale::get("menu.items.stretch_filter"), ItemKind::Toggle, [] { return std::string(Options::video.stretch_filter_linear ? Locale::get("menu.values.linear") : Locale::get("menu.values.nearest")); }, [](int) { Screen::get()->toggleStretchFilter(); }, nullptr});
p.items.push_back({Locale::get("menu.items.render_info"), ItemKind::Cycle, [] {
switch (Options::render_info.position) {
@@ -164,12 +148,9 @@ namespace Menu {
case Options::RenderInfoPosition::TOP: return std::string(Locale::get("menu.values.top"));
case Options::RenderInfoPosition::BOTTOM: return std::string(Locale::get("menu.values.bottom"));
}
return std::string(Locale::get("menu.values.off")); },
[](int dir) { Overlay::cycleRenderInfo(dir); }, nullptr});
return std::string(Locale::get("menu.values.off")); }, [](int dir) { Overlay::cycleRenderInfo(dir); }, nullptr});
p.items.push_back({Locale::get("menu.items.uptime"), ItemKind::Toggle,
[] { return onOff(Options::render_info.show_time); },
[](int) { Options::render_info.show_time = !Options::render_info.show_time; }, nullptr});
p.items.push_back({Locale::get("menu.items.uptime"), ItemKind::Toggle, [] { return onOff(Options::render_info.show_time); }, [](int) { Options::render_info.show_time = !Options::render_info.show_time; }, nullptr});
return p;
}
@@ -205,35 +186,23 @@ namespace Menu {
static Page buildAudio() {
Page p{Locale::get("menu.titles.audio"), {}, 0};
p.items.push_back({Locale::get("menu.items.master_enable"), ItemKind::Toggle,
[] { return onOff(Options::audio.enabled); }, [](int) {
p.items.push_back({Locale::get("menu.items.master_enable"), ItemKind::Toggle, [] { return onOff(Options::audio.enabled); }, [](int) {
Options::audio.enabled = !Options::audio.enabled;
Options::applyAudio(); },
nullptr});
Options::applyAudio(); }, nullptr});
p.items.push_back({Locale::get("menu.items.master_volume"), ItemKind::IntRange,
[] { return volPct(Options::audio.volume); },
[](int dir) { stepVolume(Options::audio.volume, dir); }, nullptr});
p.items.push_back({Locale::get("menu.items.master_volume"), ItemKind::IntRange, [] { return volPct(Options::audio.volume); }, [](int dir) { stepVolume(Options::audio.volume, dir); }, nullptr});
p.items.push_back({Locale::get("menu.items.music"), ItemKind::Toggle,
[] { return onOff(Options::audio.music_enabled); }, [](int) {
p.items.push_back({Locale::get("menu.items.music"), ItemKind::Toggle, [] { return onOff(Options::audio.music_enabled); }, [](int) {
Options::audio.music_enabled = !Options::audio.music_enabled;
Options::applyAudio(); },
nullptr});
Options::applyAudio(); }, nullptr});
p.items.push_back({Locale::get("menu.items.music_volume"), ItemKind::IntRange,
[] { return volPct(Options::audio.music_volume); },
[](int dir) { stepVolume(Options::audio.music_volume, dir); }, nullptr});
p.items.push_back({Locale::get("menu.items.music_volume"), ItemKind::IntRange, [] { return volPct(Options::audio.music_volume); }, [](int dir) { stepVolume(Options::audio.music_volume, dir); }, nullptr});
p.items.push_back({Locale::get("menu.items.sounds"), ItemKind::Toggle,
[] { return onOff(Options::audio.sound_enabled); }, [](int) {
p.items.push_back({Locale::get("menu.items.sounds"), ItemKind::Toggle, [] { return onOff(Options::audio.sound_enabled); }, [](int) {
Options::audio.sound_enabled = !Options::audio.sound_enabled;
Options::applyAudio(); },
nullptr});
Options::applyAudio(); }, nullptr});
p.items.push_back({Locale::get("menu.items.sounds_volume"), ItemKind::IntRange,
[] { return volPct(Options::audio.sound_volume); },
[](int dir) { stepVolume(Options::audio.sound_volume, dir); }, nullptr});
p.items.push_back({Locale::get("menu.items.sounds_volume"), ItemKind::IntRange, [] { return volPct(Options::audio.sound_volume); }, [](int dir) { stepVolume(Options::audio.sound_volume, dir); }, nullptr});
return p;
}

View File

@@ -63,6 +63,7 @@ namespace Overlay {
std::string text;
float anim{0.0F};
bool visible{false};
bool mono_digits{false}; // si true, dígits amb amplada fixa (la resta natural)
};
static InfoSegment info_segments_[INFO_SEGMENT_COUNT];
@@ -182,15 +183,19 @@ namespace Overlay {
// Render si hi ha alguna cosa visible
if (info_visible_pos_ != Options::RenderInfoPosition::OFF && info_anim_ > 0.0F) {
const int DIGIT_CELL = font_->charBoxWidth() - 1; // amplada uniforme per dígit
// Calcula amplada total interpolant cada segment per la seva anim
float total_w = 0.0F;
for (auto& seg : info_segments_) {
if (seg.anim > 0.0F && !seg.text.empty()) {
total_w += font_->width(seg.text.c_str()) * Easing::outQuad(seg.anim);
int w = seg.mono_digits
? font_->widthMonoDigits(seg.text.c_str(), DIGIT_CELL)
: font_->width(seg.text.c_str());
total_w += w * Easing::outQuad(seg.anim);
}
}
if (total_w > 0.0F) {
// Slide vertical (ease-out quadratic) — igual que abans
float eased_y = Easing::outQuad(info_anim_);
int ch = font_->charHeight();
int final_y;
@@ -209,9 +214,17 @@ namespace Overlay {
for (auto& seg : info_segments_) {
if (seg.anim > 0.01F && !seg.text.empty()) {
int xi = static_cast<int>(cur_x);
font_->draw(pixel_data, xi + 1, info_y + 1, seg.text.c_str(), Options::render_info.shadow_color);
font_->draw(pixel_data, xi, info_y, seg.text.c_str(), Options::render_info.text_color);
cur_x += font_->width(seg.text.c_str()) * Easing::outQuad(seg.anim);
int seg_w = seg.mono_digits
? font_->widthMonoDigits(seg.text.c_str(), DIGIT_CELL)
: font_->width(seg.text.c_str());
if (seg.mono_digits) {
font_->drawMonoDigits(pixel_data, xi + 1, info_y + 1, seg.text.c_str(), Options::render_info.shadow_color, DIGIT_CELL);
font_->drawMonoDigits(pixel_data, xi, info_y, seg.text.c_str(), Options::render_info.text_color, DIGIT_CELL);
} else {
font_->draw(pixel_data, xi + 1, info_y + 1, seg.text.c_str(), Options::render_info.shadow_color);
font_->draw(pixel_data, xi, info_y, seg.text.c_str(), Options::render_info.text_color);
}
cur_x += seg_w * Easing::outQuad(seg.anim);
}
}
}
@@ -268,9 +281,10 @@ namespace Overlay {
Options::render_info.position = static_cast<Options::RenderInfoPosition>(pos);
}
void setRenderInfoSegments(const char* s0, const char* s1, const char* s2, const char* s3) {
void setRenderInfoSegments(const char* s0, const char* s1, const char* s2, const char* s3, unsigned int mono_mask) {
const char* segs[INFO_SEGMENT_COUNT] = {s0, s1, s2, s3};
for (int i = 0; i < INFO_SEGMENT_COUNT; i++) {
info_segments_[i].mono_digits = (mono_mask >> i) & 1u;
if (segs[i] != nullptr && *segs[i] != '\0') {
info_segments_[i].text = segs[i];
info_segments_[i].visible = true;

View File

@@ -17,7 +17,8 @@ namespace Overlay {
void cycleRenderInfo(int dir); // dir=+1 avant, -1 endarrere
// Configura els segments del render info. Cada segment (nullptr o string buit
// per amagar) apareix/desapareix amb animació; el conjunt es centra dinàmicament.
void setRenderInfoSegments(const char* s0, const char* s1, const char* s2, const char* s3);
// `mono_mask` és un bitfield: bit N = 1 → segment N amb amplada monoespaiada.
void setRenderInfoSegments(const char* s0, const char* s1, const char* s2, const char* s3, unsigned int mono_mask = 0);
// Gestió d'eixida amb doble ESC
// Retorna true si l'ESC ha sigut consumit (no s'ha de passar al joc)

View File

@@ -3,6 +3,7 @@
#include <cstdio>
#include <iostream>
#include "core/locale/locale.hpp"
#include "core/rendering/overlay.hpp"
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
#include "game/defines.hpp"
@@ -37,7 +38,7 @@ Screen::Screen() {
int w = GAME_WIDTH * zoom_;
int h = Options::video.aspect_ratio_4_3 ? static_cast<int>(GAME_HEIGHT * 1.2F) * zoom_ : GAME_HEIGHT * zoom_;
window_ = SDL_CreateWindow(Texts::WINDOW_TITLE, w, h, fullscreen_ ? SDL_WINDOW_FULLSCREEN : 0);
window_ = SDL_CreateWindow(Locale::get("window.title"), w, h, fullscreen_ ? SDL_WINDOW_FULLSCREEN : 0);
renderer_ = SDL_CreateRenderer(window_, nullptr);
SDL_SetRenderLogicalPresentation(renderer_, GAME_WIDTH, GAME_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);
@@ -360,11 +361,14 @@ void Screen::updateRenderInfo() {
snprintf(time_buf, sizeof(time_buf), " - %d:%02d.%02d", minutes, seconds, centis);
}
// Dígits en mono a FPS (segment 0) i TEMPS (segment 3): els dígits canvien
// contínuament mentre els símbols del voltant ("fps", ":", ".", " - ") no
Overlay::setRenderInfoSegments(
fps_driver.c_str(),
shader_seg.empty() ? nullptr : shader_seg.c_str(),
ss_seg,
time_buf[0] ? time_buf : nullptr);
time_buf[0] ? time_buf : nullptr,
0b1001);
}
void Screen::adjustWindowSize() {

View File

@@ -266,6 +266,137 @@ void Text::drawClipped(Uint32* pixel_data, int x, int y, const char* text, Uint3
}
}
void Text::drawMono(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int cell_w) const {
if (!bitmap_ || !pixel_data) return;
const char* ptr = text;
int cursor_x = x;
while (*ptr) {
uint32_t cp = nextCodepoint(ptr);
if (cp == 0) break;
auto it = glyphs_.find(cp);
if (it == glyphs_.end()) {
it = glyphs_.find('?');
if (it == glyphs_.end()) {
cursor_x += cell_w;
continue;
}
}
const auto& glyph = it->second;
// Centra el glif dins la cel·la
int glyph_x = cursor_x + (cell_w - glyph.w) / 2;
for (int gy = 0; gy < box_height_; gy++) {
int dst_y = y + gy;
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) continue;
for (int gx = 0; gx < glyph.w; gx++) {
int dst_x = glyph_x + gx;
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) continue;
int src_x = glyph.x + gx;
int src_y = glyph.y + gy;
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) continue;
Uint8 pixel = bitmap_[src_x + src_y * bitmap_width_];
if (pixel != 0) {
pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color;
}
}
}
cursor_x += cell_w;
}
}
void Text::drawMonoDigits(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int digit_cell_w) const {
if (!bitmap_ || !pixel_data) return;
const char* ptr = text;
int cursor_x = x;
bool first = true;
while (*ptr) {
uint32_t cp = nextCodepoint(ptr);
if (cp == 0) break;
auto it = glyphs_.find(cp);
if (it == glyphs_.end()) {
it = glyphs_.find('?');
if (it == glyphs_.end()) {
if (!first) cursor_x += 1;
cursor_x += box_width_;
first = false;
continue;
}
}
const auto& glyph = it->second;
bool is_digit = (cp >= '0' && cp <= '9');
if (!first) cursor_x += 1; // kerning
int glyph_x = is_digit ? cursor_x + (digit_cell_w - glyph.w) / 2 : cursor_x;
for (int gy = 0; gy < box_height_; gy++) {
int dst_y = y + gy;
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) continue;
for (int gx = 0; gx < glyph.w; gx++) {
int dst_x = glyph_x + gx;
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) continue;
int src_x = glyph.x + gx;
int src_y = glyph.y + gy;
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) continue;
Uint8 pixel = bitmap_[src_x + src_y * bitmap_width_];
if (pixel != 0) {
pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color;
}
}
}
cursor_x += is_digit ? digit_cell_w : glyph.w;
first = false;
}
}
auto Text::widthMonoDigits(const char* text, int digit_cell_w) const -> int {
const char* ptr = text;
int w = 0;
bool first = true;
while (*ptr) {
uint32_t cp = nextCodepoint(ptr);
if (cp == 0) break;
if (!first) w += 1; // kerning
first = false;
bool is_digit = (cp >= '0' && cp <= '9');
if (is_digit) {
w += digit_cell_w;
} else {
auto it = glyphs_.find(cp);
if (it == glyphs_.end()) it = glyphs_.find('?');
if (it != glyphs_.end())
w += it->second.w;
else
w += box_width_;
}
}
return w;
}
auto Text::widthMono(const char* text, int cell_w) const -> int {
const char* ptr = text;
int count = 0;
while (*ptr) {
uint32_t cp = nextCodepoint(ptr);
if (cp == 0) break;
count++;
}
return count * cell_w;
}
auto Text::width(const char* text) const -> int {
const char* ptr = text;
int w = 0;

View File

@@ -17,9 +17,22 @@ class Text {
// Com draw, però clippat a [clip_x_min, clip_x_max) × [clip_y_min, clip_y_max)
void drawClipped(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) const;
// Dibuixa text monoespaiat: cada glif ocupa `cell_w` i es centra dins la cel·la.
// Útil per a comptadors (FPS, hora) on cada dígit pot tenir amplades diferents.
void drawMono(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int cell_w) const;
// Mono només per a dígits (0-9): cada dígit ocupa `digit_cell_w` centrat;
// la resta de glifs mantenen l'amplada natural. Ideal per a comptadors tipus "9:59.59".
void drawMonoDigits(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int digit_cell_w) const;
// Calcula ancho en píxeles d'un text
[[nodiscard]] auto width(const char* text) const -> int;
// Amplada mono: nombre de codepoints × cell_w
[[nodiscard]] auto widthMono(const char* text, int cell_w) const -> int;
// Amplada mono-dígits: amplada natural, però substituint els dígits per digit_cell_w
[[nodiscard]] auto widthMonoDigits(const char* text, int digit_cell_w) const -> int;
[[nodiscard]] auto charHeight() const -> int { return box_height_; }
[[nodiscard]] auto charBoxWidth() const -> int { return box_width_; }
private:
struct GlyphInfo {