- afegida carpeta release
- jitter en renderInfo
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user