overlay amb outline
afegits credits dels ports
This commit is contained in:
@@ -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_;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user