overlay amb outline

afegits credits dels ports
This commit is contained in:
2026-04-05 15:17:26 +02:00
parent ec0f233033
commit 6a58f75773
4 changed files with 212 additions and 16 deletions

View File

@@ -39,7 +39,11 @@ namespace Overlay {
FINISHED };
struct Notification {
std::string message;
std::vector<std::string> lines; // una o més línies de text
Overlay::NotifPosition pos{Overlay::NotifPosition::TOP_LEFT_SLIDE};
Overlay::NotifStyle style{Overlay::NotifStyle::BOX};
Uint32 accent_color{NOTIF_BG_COLOR}; // fons (BOX) o ombra (SHADOW)
Uint32 text_color{NOTIF_TEXT_COLOR};
Status status{Status::RISING};
float anim{0.0F}; // 0 = fora de pantalla, 1 = posició final
float timer{0.0F};
@@ -67,6 +71,21 @@ namespace Overlay {
};
static InfoSegment info_segments_[INFO_SEGMENT_COUNT];
// --- Crèdits cinematogràfics ---
// Usen el sistema de notificacions en posició TOP_CENTER_DROP.
enum class CreditsPhase { IDLE,
DELAY,
PLAYING_1,
GAP,
PLAYING_2 };
static CreditsPhase credits_phase_ = CreditsPhase::IDLE;
static float credits_timer_ = 0.0F; // segons dins la phase actual
static constexpr float CREDITS_DELAY = 2.0F;
static constexpr float CREDITS_GAP = 0.4F;
static constexpr float CREDITS_HOLD = 7.5F;
static constexpr Uint32 CREDITS_BG = NOTIF_BG_COLOR; // mateix marró de les notificacions
static constexpr Uint32 CREDITS_FG = NOTIF_TEXT_COLOR; // mateix cian
// --- Doble ESC per a eixir ---
static bool esc_waiting_ = false;
@@ -131,17 +150,49 @@ namespace Overlay {
if (notif.status == Status::FINISHED) continue;
// Posició: entra des de l'esquerra
int target_x = NOTIF_MARGIN_X;
int target_y = NOTIF_MARGIN_Y;
int box_x = target_x - static_cast<int>((1.0F - notif.anim) * (notif.box_w + NOTIF_MARGIN_X));
int box_y = target_y;
// Posició segons el tipus
int box_x = 0;
int box_y = 0;
switch (notif.pos) {
case NotifPosition::TOP_LEFT_SLIDE: {
int target_x = NOTIF_MARGIN_X;
int target_y = NOTIF_MARGIN_Y;
box_x = target_x - static_cast<int>((1.0F - notif.anim) * (notif.box_w + NOTIF_MARGIN_X));
box_y = target_y;
break;
}
case NotifPosition::TOP_CENTER_DROP: {
int target_y = NOTIF_MARGIN_Y;
box_x = (SCREEN_W - notif.box_w) / 2;
// Baixa des de sobre de la pantalla fins a target_y
box_y = target_y - static_cast<int>((1.0F - notif.anim) * (notif.box_h + NOTIF_MARGIN_Y));
break;
}
}
// Pinta fons de la caixa
drawRect(pixel_data, box_x, box_y, notif.box_w, notif.box_h, NOTIF_BG_COLOR);
// Pinta fons (si BOX)
if (notif.style == NotifStyle::BOX) {
drawRect(pixel_data, box_x, box_y, notif.box_w, notif.box_h, notif.accent_color);
}
// Pinta text dins la caixa
font_->draw(pixel_data, box_x + NOTIF_PADDING_H, box_y + NOTIF_PADDING_V, notif.message.c_str(), NOTIF_TEXT_COLOR);
// Pinta el text línia a línia (amb ombra o contorn segons style)
int line_h = font_->charHeight();
int line_y = box_y + NOTIF_PADDING_V;
for (const auto& line : notif.lines) {
int line_w = font_->width(line.c_str());
int line_x = box_x + (notif.box_w - line_w) / 2; // centrat dins la caixa
if (notif.style == NotifStyle::SHADOW) {
font_->draw(pixel_data, line_x + 1, line_y + 1, line.c_str(), notif.accent_color);
} else if (notif.style == NotifStyle::OUTLINE) {
// Contorn 4-direccional (N, S, E, W)
font_->draw(pixel_data, line_x, line_y - 1, line.c_str(), notif.accent_color);
font_->draw(pixel_data, line_x, line_y + 1, line.c_str(), notif.accent_color);
font_->draw(pixel_data, line_x - 1, line_y, line.c_str(), notif.accent_color);
font_->draw(pixel_data, line_x + 1, line_y, line.c_str(), notif.accent_color);
}
font_->draw(pixel_data, line_x, line_y, line.c_str(), notif.text_color);
line_y += line_h + 1;
}
}
// Render info (FPS, driver, shader) — animat amb slide vertical
@@ -247,12 +298,69 @@ namespace Overlay {
int w = font_->width(pause_text);
int x = SCREEN_W - w - 4;
int y = 4;
// Ombra
font_->draw(pixel_data, x + 1, y + 1, pause_text, 0xFF000000);
// Contorn blanc 4-direccional
font_->draw(pixel_data, x, y - 1, pause_text, 0xFFFFFFFF);
font_->draw(pixel_data, x, y + 1, pause_text, 0xFFFFFFFF);
font_->draw(pixel_data, x - 1, y, pause_text, 0xFFFFFFFF);
font_->draw(pixel_data, x + 1, y, pause_text, 0xFFFFFFFF);
// Text en roig
font_->draw(pixel_data, x, y, pause_text, 0xFF0000FF);
}
// Crèdits seqüencials — dispara notificacions TOP_CENTER_DROP una darrere l'altra.
if (credits_phase_ != CreditsPhase::IDLE) {
credits_timer_ += dt;
switch (credits_phase_) {
case CreditsPhase::DELAY:
if (credits_timer_ >= CREDITS_DELAY) {
showNotification(
{std::string(Locale::get("credits.port_role")),
std::string(Locale::get("credits.port_name"))},
CREDITS_HOLD,
NotifPosition::TOP_CENTER_DROP,
NotifStyle::OUTLINE,
CREDITS_BG,
CREDITS_FG);
credits_phase_ = CreditsPhase::PLAYING_1;
credits_timer_ = 0.0F;
}
break;
case CreditsPhase::PLAYING_1:
if (notifications_.empty()) {
credits_phase_ = CreditsPhase::GAP;
credits_timer_ = 0.0F;
}
break;
case CreditsPhase::GAP:
if (credits_timer_ >= CREDITS_GAP) {
showNotification(
{std::string(Locale::get("credits.modern_role")),
std::string(Locale::get("credits.modern_name"))},
CREDITS_HOLD,
NotifPosition::TOP_CENTER_DROP,
NotifStyle::OUTLINE,
CREDITS_BG,
CREDITS_FG);
credits_phase_ = CreditsPhase::PLAYING_2;
credits_timer_ = 0.0F;
}
break;
case CreditsPhase::PLAYING_2:
if (notifications_.empty()) {
credits_phase_ = CreditsPhase::IDLE;
credits_timer_ = 0.0F;
}
break;
case CreditsPhase::IDLE:
break;
}
}
// Neteja notificacions finalitzades
notifications_.erase(
std::remove_if(notifications_.begin(), notifications_.end(), [](const Notification& n) { return n.status == Status::FINISHED; }),
notifications_.end());
// Menú flotant per damunt de tot
if (Menu::isOpen()) {
Menu::render(pixel_data);
@@ -260,14 +368,36 @@ namespace Overlay {
}
void showNotification(const char* text, float duration_seconds) {
showNotification({std::string(text)}, duration_seconds, NotifPosition::TOP_LEFT_SLIDE, NotifStyle::BOX, NOTIF_BG_COLOR, NOTIF_TEXT_COLOR);
}
void showNotification(const std::vector<std::string>& lines,
float duration_seconds,
NotifPosition pos,
NotifStyle style,
Uint32 accent_color,
Uint32 text_color) {
// Reemplaça la notificació anterior
notifications_.clear();
Notification notif;
notif.message = text;
notif.lines = lines;
notif.pos = pos;
notif.style = style;
notif.accent_color = accent_color;
notif.text_color = text_color;
notif.duration = duration_seconds;
notif.box_w = font_->width(text) + NOTIF_PADDING_H * 2;
notif.box_h = font_->charHeight() + NOTIF_PADDING_V * 2;
// Calcula l'amplada màxima de les línies
int max_w = 0;
for (const auto& line : lines) {
int w = font_->width(line.c_str());
if (w > max_w) max_w = w;
}
notif.box_w = max_w + NOTIF_PADDING_H * 2;
int line_h = font_->charHeight();
int line_count = static_cast<int>(lines.size());
notif.box_h = line_count * line_h + (line_count - 1) * 1 + NOTIF_PADDING_V * 2;
notifications_.push_back(notif);
}
@@ -294,6 +424,22 @@ namespace Overlay {
}
}
void startCredits() {
if (credits_phase_ != CreditsPhase::IDLE) return;
credits_phase_ = CreditsPhase::DELAY;
credits_timer_ = 0.0F;
}
void cancelCredits() {
credits_phase_ = CreditsPhase::IDLE;
credits_timer_ = 0.0F;
notifications_.clear();
}
auto creditsActive() -> bool {
return credits_phase_ != CreditsPhase::IDLE;
}
auto isEscConsumed() -> bool {
return esc_waiting_;
}

View File

@@ -2,6 +2,9 @@
#include <SDL3/SDL.h>
#include <string>
#include <vector>
namespace Overlay {
void init();
void destroy();
@@ -9,9 +12,31 @@ namespace Overlay {
// Pinta l'overlay sobre el buffer ARGB — cridar abans de presentar
void render(Uint32* pixel_data);
// Mostra una notificació amb animació slide-in/stay/slide-out
// Posició + animació d'una notificació
enum class NotifPosition {
TOP_LEFT_SLIDE, // Cantó superior esquerra, slide horizontal des de fora
TOP_CENTER_DROP, // Centrat horitzontal, baixa des de sobre
};
// Estil de la notificació: caixa de fons, ombra o contorn del text
enum class NotifStyle {
BOX, // Rectangle de fons amb accent_color
SHADOW, // Sense fons, text amb ombra (offset +1,+1) en accent_color
OUTLINE, // Sense fons, text amb contorn 4-direccional en accent_color
};
// Mostra una notificació amb animació slide-in/stay/slide-out (API simple)
void showNotification(const char* text, float duration_seconds = 2.0F);
// Versió extensa: múltiples línies, posició, estil, colors i durada personalitzats.
// accent_color s'usa com a fons (si style=BOX) o com a ombra (si style=SHADOW).
void showNotification(const std::vector<std::string>& lines,
float duration_seconds,
NotifPosition pos,
NotifStyle style,
Uint32 accent_color,
Uint32 text_color);
// Activa/desactiva la info de renderitzat (FPS, driver, shader, preset)
void toggleRenderInfo();
void cycleRenderInfo(int dir); // dir=+1 avant, -1 endarrere
@@ -20,6 +45,11 @@ namespace Overlay {
// `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);
// Crèdits seqüencials cinematogràfics (2 blocs fade-in/hold/fade-out)
void startCredits();
void cancelCredits();
auto creditsActive() -> bool;
// Gestió d'eixida amb doble ESC
// Retorna true si l'ESC ha sigut consumit (no s'ha de passar al joc)
auto handleEscape() -> bool;