treballant en el overlay, el text i les notificacions

This commit is contained in:
2026-04-04 18:11:04 +02:00
parent abb23071b5
commit fe8e5d661e
38 changed files with 738 additions and 105 deletions

View File

@@ -1,37 +0,0 @@
#include "core/global_inputs.hpp"
#include <SDL3/SDL.h>
#include "core/jinput.hpp"
#include "core/screen.hpp"
namespace GlobalInputs {
static bool f1_was_pressed = false;
static bool f2_was_pressed = false;
static bool f3_was_pressed = false;
void handle() {
// F1 — decrement zoom
bool f1 = JI_KeyPressed(SDL_SCANCODE_F1);
if (f1 && !f1_was_pressed) {
Screen::get()->decZoom();
}
f1_was_pressed = f1;
// F2 — increment zoom
bool f2 = JI_KeyPressed(SDL_SCANCODE_F2);
if (f2 && !f2_was_pressed) {
Screen::get()->incZoom();
}
f2_was_pressed = f2;
// F3 — toggle fullscreen
bool f3 = JI_KeyPressed(SDL_SCANCODE_F3);
if (f3 && !f3_was_pressed) {
Screen::get()->toggleFullscreen();
}
f3_was_pressed = f3;
}
} // namespace GlobalInputs

View File

@@ -0,0 +1,47 @@
#include "core/input/global_inputs.hpp"
#include <cstdio>
#include <string>
#include "core/jail/jinput.hpp"
#include "core/rendering/overlay.hpp"
#include "core/rendering/screen.hpp"
#include "game/options.hpp"
namespace GlobalInputs {
static bool dec_zoom_was_pressed = false;
static bool inc_zoom_was_pressed = false;
static bool fullscreen_was_pressed = false;
void handle() {
// Decrement zoom
bool dec_zoom = JI_KeyPressed(Options::keys_gui.dec_zoom);
if (dec_zoom && !dec_zoom_was_pressed) {
Screen::get()->decZoom();
char msg[32];
snprintf(msg, sizeof(msg), "ZOOM %dx", Screen::get()->getZoom());
Overlay::showNotification(msg);
}
dec_zoom_was_pressed = dec_zoom;
// Increment zoom
bool inc_zoom = JI_KeyPressed(Options::keys_gui.inc_zoom);
if (inc_zoom && !inc_zoom_was_pressed) {
Screen::get()->incZoom();
char msg[32];
snprintf(msg, sizeof(msg), "ZOOM %dx", Screen::get()->getZoom());
Overlay::showNotification(msg);
}
inc_zoom_was_pressed = inc_zoom;
// Toggle fullscreen
bool fullscreen = JI_KeyPressed(Options::keys_gui.fullscreen);
if (fullscreen && !fullscreen_was_pressed) {
Screen::get()->toggleFullscreen();
Overlay::showNotification(Screen::get()->isFullscreen() ? "FULLSCREEN" : "WINDOWED");
}
fullscreen_was_pressed = fullscreen;
}
} // namespace GlobalInputs

View File

@@ -1,5 +1,5 @@
#ifndef JA_USESDLMIXER
#include "core/jail_audio.hpp"
#include "core/jail/jail_audio.hpp"
#include <SDL3/SDL.h>
#include <stdio.h>

View File

@@ -1,9 +1,9 @@
#include "core/jdraw8.hpp"
#include "core/jail/jdraw8.hpp"
#include <fstream>
#include "core/jfile.hpp"
#include "core/screen.hpp"
#include "core/jail/jfile.hpp"
#include "core/rendering/screen.hpp"
#include "external/gif.h"
JD8_Surface screen = NULL;

View File

@@ -1,4 +1,4 @@
#include "core/jfile.hpp"
#include "core/jail/jfile.hpp"
#include <stdint.h>
#include <stdio.h>

View File

@@ -1,4 +1,4 @@
#include "core/jgame.hpp"
#include "core/jail/jgame.hpp"
bool eixir = false;
Uint32 updateTicks = 0;

View File

@@ -1,9 +1,9 @@
#include "core/jinput.hpp"
#include "core/jail/jinput.hpp"
#include <string>
#include "core/global_inputs.hpp"
#include "core/jgame.hpp"
#include "core/input/global_inputs.hpp"
#include "core/jail/jgame.hpp"
const bool* keystates; // = SDL_GetKeyboardState( NULL );
SDL_Event event;

View File

@@ -0,0 +1,119 @@
#include "core/rendering/overlay.hpp"
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include "core/rendering/text.hpp"
namespace Overlay {
static std::unique_ptr<Text> font_;
// --- Notificacions amb animació ---
enum class Status { RISING, STAY, VANISHING, FINISHED };
struct Notification {
std::string message;
Status status{Status::RISING};
float y_offset{0.0F}; // 0 = fora de pantalla, 1 = posició final
float timer{0.0F};
float duration{2.0F};
};
static std::vector<Notification> notifications_;
static Uint32 last_ticks_ = 0;
static constexpr float SLIDE_SPEED = 4.0F; // velocitat d'animació (unitats/segon)
static constexpr int BAR_HEIGHT = 12; // alçada de la barra
static constexpr int TEXT_Y_OFFSET = 2; // offset del text dins la barra
static constexpr Uint32 BG_COLOR = 0xFF1A1A2E; // fons blau fosc
static constexpr Uint32 TEXT_COLOR = 0xFF00FFFF; // cyan
static constexpr int SCREEN_W = 320;
static constexpr int SCREEN_H = 200;
void init() {
font_ = std::make_unique<Text>("fonts/8bithud.fnt", "fonts/8bithud.gif");
last_ticks_ = SDL_GetTicks();
}
void destroy() {
font_.reset();
notifications_.clear();
}
static void drawBar(Uint32* pixel_data, int y, int h, Uint32 color) {
for (int row = y; row < y + h; row++) {
if (row < 0 || row >= SCREEN_H) continue;
for (int col = 0; col < SCREEN_W; col++) {
pixel_data[col + row * SCREEN_W] = color;
}
}
}
void render(Uint32* pixel_data) {
if (!font_ || !pixel_data) return;
// Calcula delta time
Uint32 now = SDL_GetTicks();
float dt = static_cast<float>(now - last_ticks_) / 1000.0F;
last_ticks_ = now;
// Actualitza i pinta cada notificació
for (auto& notif : notifications_) {
switch (notif.status) {
case Status::RISING:
notif.y_offset += SLIDE_SPEED * dt;
if (notif.y_offset >= 1.0F) {
notif.y_offset = 1.0F;
notif.status = Status::STAY;
notif.timer = 0.0F;
}
break;
case Status::STAY:
notif.timer += dt;
if (notif.timer >= notif.duration) {
notif.status = Status::VANISHING;
}
break;
case Status::VANISHING:
notif.y_offset -= SLIDE_SPEED * dt;
if (notif.y_offset <= 0.0F) {
notif.status = Status::FINISHED;
}
break;
case Status::FINISHED:
break;
}
if (notif.status == Status::FINISHED) continue;
// Posició: puja des de sota la pantalla
int bar_y = SCREEN_H - static_cast<int>(notif.y_offset * BAR_HEIGHT);
// Pinta fons
drawBar(pixel_data, bar_y, BAR_HEIGHT, BG_COLOR);
// Pinta text centrat
font_->drawCentered(pixel_data, bar_y + TEXT_Y_OFFSET, notif.message.c_str(), TEXT_COLOR);
}
// Elimina les acabades
notifications_.erase(
std::remove_if(notifications_.begin(), notifications_.end(),
[](const Notification& n) { return n.status == Status::FINISHED; }),
notifications_.end());
}
void showNotification(const char* text, float duration_seconds) {
// Si ja hi ha una notificació, la reemplaça (no apilem)
notifications_.clear();
notifications_.push_back({text, Status::RISING, 0.0F, 0.0F, duration_seconds});
}
} // namespace Overlay

View File

@@ -0,0 +1,14 @@
#pragma once
#include <SDL3/SDL.h>
namespace Overlay {
void init();
void destroy();
// 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
void showNotification(const char* text, float duration_seconds = 2.0F);
} // namespace Overlay

View File

@@ -1,7 +1,8 @@
#include "core/screen.hpp"
#include "core/rendering/screen.hpp"
#include <iostream>
#include "core/rendering/overlay.hpp"
#include "game/defines.hpp"
#include "game/options.hpp"
@@ -53,7 +54,8 @@ Screen::~Screen() {
if (window_) SDL_DestroyWindow(window_);
}
void Screen::present(const Uint32* pixel_data) {
void Screen::present(Uint32* pixel_data) {
Overlay::render(pixel_data);
SDL_UpdateTexture(texture_, nullptr, pixel_data, GAME_WIDTH * sizeof(Uint32));
SDL_RenderClear(renderer_);
SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);

View File

@@ -11,7 +11,7 @@ class Screen {
static auto get() -> Screen*;
// Presentació — rep el buffer ARGB de 320x200 de JD8
void present(const Uint32* pixel_data);
void present(Uint32* pixel_data);
// Gestió de finestra
void toggleFullscreen();

View File

@@ -0,0 +1,239 @@
#include "core/rendering/text.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include "core/jail/jfile.hpp"
// Forward declarations de gif.h (inclòs des de jdraw8.cpp, no es pot incloure dos vegades)
struct rgb;
extern unsigned char* LoadGif(unsigned char* data, unsigned short* w, unsigned short* h);
Text::Text(const char* fnt_file, const char* gif_file) {
loadBitmap(gif_file);
loadFont(fnt_file);
}
Text::~Text() {
if (bitmap_) free(bitmap_);
}
// --- UTF-8 ---
auto Text::nextCodepoint(const char*& ptr) -> uint32_t {
auto byte = static_cast<uint8_t>(*ptr);
if (byte == 0) return 0;
uint32_t cp = 0;
int extra = 0;
if (byte < 0x80) {
cp = byte;
} else if ((byte & 0xE0) == 0xC0) {
cp = byte & 0x1F;
extra = 1;
} else if ((byte & 0xF0) == 0xE0) {
cp = byte & 0x0F;
extra = 2;
} else if ((byte & 0xF8) == 0xF0) {
cp = byte & 0x07;
extra = 3;
} else {
ptr++;
return 0xFFFD;
}
ptr++;
for (int i = 0; i < extra; i++) {
auto cont = static_cast<uint8_t>(*ptr);
if ((cont & 0xC0) != 0x80) return 0xFFFD;
cp = (cp << 6) | (cont & 0x3F);
ptr++;
}
return cp;
}
// --- Càrrega de font ---
void Text::loadFont(const char* fnt_file) {
int filesize = 0;
char* buffer = file_getfilebuffer(fnt_file, filesize, true);
if (!buffer) {
std::cerr << "Text: unable to load font file: " << fnt_file << '\n';
return;
}
std::istringstream stream(std::string(buffer, filesize));
free(buffer);
std::string line;
int glyph_index = 0;
while (std::getline(stream, line)) {
// Ignora comentaris i línies buides
if (line.empty() || line[0] == '#') continue;
// Elimina comentaris inline
auto comment_pos = line.find('#');
if (comment_pos != std::string::npos) {
line = line.substr(0, comment_pos);
}
// Parseja directives
if (line.find("box_width") == 0) {
sscanf(line.c_str(), "box_width %d", &box_width_);
continue;
}
if (line.find("box_height") == 0) {
sscanf(line.c_str(), "box_height %d", &box_height_);
continue;
}
if (line.find("columns") == 0) {
sscanf(line.c_str(), "columns %d", &columns_);
continue;
}
if (line.find("cell_spacing") == 0) {
sscanf(line.c_str(), "cell_spacing %d", &cell_spacing_);
continue;
}
if (line.find("row_spacing") == 0) {
sscanf(line.c_str(), "row_spacing %d", &row_spacing_);
continue;
}
// Línies de glifos: "codepoint width"
uint32_t codepoint = 0;
int visual_width = 0;
if (sscanf(line.c_str(), "%u %d", &codepoint, &visual_width) == 2) {
int col = glyph_index % columns_;
int row = glyph_index / columns_;
GlyphInfo glyph{};
glyph.x = col * (box_width_ + cell_spacing_) + cell_spacing_;
glyph.y = row * (box_height_ + (row_spacing_ > 0 ? row_spacing_ : cell_spacing_)) + cell_spacing_;
glyph.w = visual_width;
glyphs_[codepoint] = glyph;
glyph_index++;
}
}
std::cout << "Text: loaded font with " << glyphs_.size() << " glyphs (" << box_width_ << "x" << box_height_ << ")\n";
}
void Text::loadBitmap(const char* gif_file) {
int filesize = 0;
char* buffer = file_getfilebuffer(gif_file, filesize);
if (!buffer) {
std::cerr << "Text: unable to load bitmap: " << gif_file << '\n';
return;
}
// Extrau dimensions del header GIF (bytes 6-7 = width, 8-9 = height, little-endian)
auto* raw = reinterpret_cast<unsigned char*>(buffer);
int w = raw[6] | (raw[7] << 8);
int h = raw[8] | (raw[9] << 8);
unsigned short gw = 0, gh = 0;
Uint8* pixels = LoadGif(raw, &gw, &gh);
if (!pixels) {
std::cerr << "Text: unable to decode GIF: " << gif_file << '\n';
free(buffer);
return;
}
bitmap_width_ = w;
bitmap_height_ = h;
bitmap_ = pixels;
free(buffer);
std::cout << "Text: bitmap loaded " << w << "x" << h << '\n';
}
// --- Renderitzat ---
void Text::draw(Uint32* pixel_data, int x, int y, const char* text, Uint32 color) 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 += box_width_;
continue;
}
}
const auto& glyph = it->second;
// Pinta glifo pixel a pixel
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 = cursor_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_];
// Píxel no transparent (índex 0 és fons típicament)
if (pixel != 0) {
pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color;
}
}
}
cursor_x += glyph.w + 1; // +1 kerning
}
}
void Text::drawCentered(Uint32* pixel_data, int y, const char* text, Uint32 color) const {
int w = width(text);
int x = (SCREEN_WIDTH - w) / 2;
draw(pixel_data, x, y, text, color);
}
auto Text::width(const char* text) const -> int {
const char* ptr = text;
int w = 0;
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 (!first) w += 1; // kerning
first = false;
if (it != glyphs_.end()) {
w += it->second.w;
} else {
w += box_width_;
}
}
return w;
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include <SDL3/SDL.h>
#include <cstdint>
#include <string>
#include <unordered_map>
class Text {
public:
Text(const char* fnt_file, const char* gif_file);
~Text();
// Pinta texto sobre un buffer ARGB de 320x200
void draw(Uint32* pixel_data, int x, int y, const char* text, Uint32 color) const;
void drawCentered(Uint32* pixel_data, int y, const char* text, Uint32 color) const;
// Calcula ancho en píxeles d'un text
[[nodiscard]] auto width(const char* text) const -> int;
[[nodiscard]] auto charHeight() const -> int { return box_height_; }
private:
struct GlyphInfo {
int x, y; // posició en el bitmap
int w; // ample visual
};
int box_width_{0};
int box_height_{0};
int columns_{0};
int cell_spacing_{0};
int row_spacing_{0};
Uint8* bitmap_{nullptr}; // píxels 8-bit del GIF de la font
int bitmap_width_{0};
int bitmap_height_{0};
std::unordered_map<uint32_t, GlyphInfo> glyphs_;
static auto nextCodepoint(const char*& ptr) -> uint32_t;
void loadFont(const char* fnt_file);
void loadBitmap(const char* gif_file);
static constexpr int SCREEN_WIDTH = 320;
static constexpr int SCREEN_HEIGHT = 200;
};

View File

@@ -2,7 +2,7 @@
#include <stdlib.h>
#include "core/jgame.hpp"
#include "core/jail/jgame.hpp"
Bola::Bola(JD8_Surface gfx, Prota* sam)
: Sprite(gfx) {

View File

@@ -1,5 +1,23 @@
#pragma once
#include <SDL3/SDL.h>
// Tecles GUI (capa de presentació — finestra, zoom, etc.)
namespace Defaults::KeysGUI {
constexpr SDL_Scancode DEC_ZOOM = SDL_SCANCODE_F1;
constexpr SDL_Scancode INC_ZOOM = SDL_SCANCODE_F2;
constexpr SDL_Scancode FULLSCREEN = SDL_SCANCODE_F3;
} // namespace Defaults::KeysGUI
// Tecles de joc (moviment del personatge, accions)
namespace Defaults::KeysGame {
constexpr SDL_Scancode UP = SDL_SCANCODE_UP;
constexpr SDL_Scancode DOWN = SDL_SCANCODE_DOWN;
constexpr SDL_Scancode LEFT = SDL_SCANCODE_LEFT;
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_RIGHT;
constexpr SDL_Scancode EXIT = SDL_SCANCODE_ESCAPE;
} // namespace Defaults::KeysGame
namespace Defaults::Audio {
constexpr float VOLUME = 1.0F;
constexpr bool MUSIC_ENABLED = true;

View File

@@ -2,7 +2,7 @@
#include <stdlib.h>
#include "core/jgame.hpp"
#include "core/jail/jgame.hpp"
Engendro::Engendro(JD8_Surface gfx, Uint16 x, Uint16 y)
: Sprite(gfx) {

View File

@@ -2,8 +2,8 @@
#include <stdlib.h>
#include "core/jgame.hpp"
#include "core/jinput.hpp"
#include "core/jail/jgame.hpp"
#include "core/jail/jinput.hpp"
Mapa::Mapa(JD8_Surface gfx, Prota* sam) {
this->gfx = gfx;

View File

@@ -1,6 +1,6 @@
#pragma once
#include "core/jdraw8.hpp"
#include "core/jail/jdraw8.hpp"
#include "game/info.hpp"
#include "game/prota.hpp"

View File

@@ -1,6 +1,6 @@
#pragma once
#include "core/jdraw8.hpp"
#include "core/jail/jdraw8.hpp"
#include "game/info.hpp"
#include "game/prota.hpp"

View File

@@ -1,10 +1,10 @@
#include "game/modulegame.hpp"
#include "core/jail_audio.hpp"
#include "core/jdraw8.hpp"
#include "core/jfile.hpp"
#include "core/jgame.hpp"
#include "core/jinput.hpp"
#include "core/jail/jail_audio.hpp"
#include "core/jail/jdraw8.hpp"
#include "core/jail/jfile.hpp"
#include "core/jail/jgame.hpp"
#include "core/jail/jinput.hpp"
ModuleGame::ModuleGame() {
this->gfx = JD8_LoadSurface(info::pepe_activat ? "frames2.gif" : "frames.gif");

View File

@@ -4,11 +4,11 @@
#include <string>
#include "core/jail_audio.hpp"
#include "core/jdraw8.hpp"
#include "core/jfile.hpp"
#include "core/jgame.hpp"
#include "core/jinput.hpp"
#include "core/jail/jail_audio.hpp"
#include "core/jail/jdraw8.hpp"
#include "core/jail/jfile.hpp"
#include "core/jail/jgame.hpp"
#include "core/jail/jinput.hpp"
ModuleSequence::ModuleSequence() {
}

View File

@@ -2,7 +2,7 @@
#include <stdlib.h>
#include "core/jgame.hpp"
#include "core/jail/jgame.hpp"
Momia::Momia(JD8_Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam)
: Sprite(gfx) {

View File

@@ -7,6 +7,22 @@
namespace Options {
// Tecles GUI (finestra, zoom)
struct KeysGUI {
SDL_Scancode dec_zoom{Defaults::KeysGUI::DEC_ZOOM};
SDL_Scancode inc_zoom{Defaults::KeysGUI::INC_ZOOM};
SDL_Scancode fullscreen{Defaults::KeysGUI::FULLSCREEN};
};
// Tecles de joc (moviment, accions)
struct KeysGame {
SDL_Scancode up{Defaults::KeysGame::UP};
SDL_Scancode down{Defaults::KeysGame::DOWN};
SDL_Scancode left{Defaults::KeysGame::LEFT};
SDL_Scancode right{Defaults::KeysGame::RIGHT};
SDL_Scancode exit{Defaults::KeysGame::EXIT};
};
// Opcions d'àudio
struct Audio {
bool music_enabled{Defaults::Audio::MUSIC_ENABLED};
@@ -31,6 +47,8 @@ namespace Options {
// --- Variables globals ---
inline std::string version{};
inline KeysGUI keys_gui{};
inline KeysGame keys_game{};
inline Audio audio{};
inline Window window{};
inline Game game{};

View File

@@ -2,8 +2,8 @@
#include <stdlib.h>
#include "core/jgame.hpp"
#include "core/jinput.hpp"
#include "core/jail/jgame.hpp"
#include "core/jail/jinput.hpp"
Prota::Prota(JD8_Surface gfx)
: Sprite(gfx) {

View File

@@ -1,6 +1,6 @@
#pragma once
#include "core/jdraw8.hpp"
#include "core/jail/jdraw8.hpp"
struct Frame {
Uint16 x;

View File

@@ -1,12 +1,13 @@
#include <ctime>
#include <string>
#include "core/global_inputs.hpp"
#include "core/jail_audio.hpp"
#include "core/jdraw8.hpp"
#include "core/jfile.hpp"
#include "core/jgame.hpp"
#include "core/screen.hpp"
#include "core/input/global_inputs.hpp"
#include "core/jail/jail_audio.hpp"
#include "core/jail/jdraw8.hpp"
#include "core/jail/jfile.hpp"
#include "core/jail/jgame.hpp"
#include "core/rendering/overlay.hpp"
#include "core/rendering/screen.hpp"
#include "game/defines.hpp"
#include "game/info.hpp"
#include "game/modulegame.hpp"
@@ -25,6 +26,7 @@ int main(int argc, char* args[]) {
Screen::init();
JD8_Init();
JA_Init(48000, SDL_AUDIO_S16, 2);
Overlay::init();
info::num_habitacio = Options::game.habitacio_inicial;
info::num_piramide = Options::game.piramide_inicial;
@@ -62,6 +64,7 @@ int main(int argc, char* args[]) {
Options::saveToFile();
Overlay::destroy();
JA_Quit();
JD8_Quit();
Screen::destroy();