578 lines
25 KiB
C++
578 lines
25 KiB
C++
#include "GameRenderer.h"
|
|
#include "../../gameplay/core/Game.h"
|
|
#include "../ui/Font.h"
|
|
#include "../../gameplay/effects/LineEffect.h"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
|
|
// Color constants (copied from main.cpp)
|
|
static const SDL_Color COLORS[] = {
|
|
{0, 0, 0, 255}, // 0: BLACK (empty)
|
|
{0, 255, 255, 255}, // 1: I-piece - cyan
|
|
{255, 255, 0, 255}, // 2: O-piece - yellow
|
|
{128, 0, 128, 255}, // 3: T-piece - purple
|
|
{0, 255, 0, 255}, // 4: S-piece - green
|
|
{255, 0, 0, 255}, // 5: Z-piece - red
|
|
{0, 0, 255, 255}, // 6: J-piece - blue
|
|
{255, 165, 0, 255} // 7: L-piece - orange
|
|
};
|
|
|
|
void GameRenderer::drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color c) {
|
|
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
|
SDL_FRect fr{x, y, w, h};
|
|
SDL_RenderFillRect(renderer, &fr);
|
|
}
|
|
|
|
void GameRenderer::drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType) {
|
|
if (!blocksTex || blockType < 0 || blockType >= PIECE_COUNT) {
|
|
// Fallback to colored rectangle if texture isn't available
|
|
SDL_Color color = (blockType >= 0 && blockType < PIECE_COUNT) ? COLORS[blockType + 1] : SDL_Color{128, 128, 128, 255};
|
|
drawRect(renderer, x, y, size-1, size-1, color);
|
|
return;
|
|
}
|
|
|
|
// JavaScript uses: sx = type * spriteSize, sy = 0, with 2px padding
|
|
// Each sprite is 90px wide in the horizontal sprite sheet
|
|
const int SPRITE_SIZE = 90;
|
|
float srcX = blockType * SPRITE_SIZE + 2; // Add 2px padding like JS
|
|
float srcY = 2; // Add 2px padding from top like JS
|
|
float srcW = SPRITE_SIZE - 4; // Subtract 4px total padding like JS
|
|
float srcH = SPRITE_SIZE - 4; // Subtract 4px total padding like JS
|
|
|
|
SDL_FRect srcRect = {srcX, srcY, srcW, srcH};
|
|
SDL_FRect dstRect = {x, y, size, size};
|
|
SDL_RenderTexture(renderer, blocksTex, &srcRect, &dstRect);
|
|
}
|
|
|
|
void GameRenderer::drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, const Game::Piece& piece, float ox, float oy, float tileSize, bool isGhost) {
|
|
if (piece.type >= PIECE_COUNT) return;
|
|
|
|
for (int cy = 0; cy < 4; ++cy) {
|
|
for (int cx = 0; cx < 4; ++cx) {
|
|
if (Game::cellFilled(piece, cx, cy)) {
|
|
float px = ox + (piece.x + cx) * tileSize;
|
|
float py = oy + (piece.y + cy) * tileSize;
|
|
|
|
if (isGhost) {
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
|
|
// Draw ghost piece as barely visible gray outline
|
|
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 20); // Very faint gray
|
|
SDL_FRect rect = {px + 2, py + 2, tileSize - 4, tileSize - 4};
|
|
SDL_RenderFillRect(renderer, &rect);
|
|
|
|
// Draw thin gray border
|
|
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 30);
|
|
SDL_FRect border = {px + 1, py + 1, tileSize - 2, tileSize - 2};
|
|
SDL_RenderRect(renderer, &border);
|
|
} else {
|
|
drawBlockTexture(renderer, blocksTex, px, py, tileSize, piece.type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize) {
|
|
if (pieceType >= PIECE_COUNT) return;
|
|
|
|
// Use the first rotation (index 0) for preview
|
|
Game::Piece previewPiece;
|
|
previewPiece.type = pieceType;
|
|
previewPiece.rot = 0;
|
|
previewPiece.x = 0;
|
|
previewPiece.y = 0;
|
|
|
|
// Center the piece in the preview area
|
|
float offsetX = 0, offsetY = 0;
|
|
if (pieceType == 0) { offsetX = tileSize * 0.5f; } // I-piece centering (assuming I = 0)
|
|
else if (pieceType == 1) { offsetX = tileSize * 0.5f; } // O-piece centering (assuming O = 1)
|
|
|
|
// Use semi-transparent alpha for preview blocks
|
|
Uint8 previewAlpha = 180;
|
|
if (blocksTex) {
|
|
SDL_SetTextureAlphaMod(blocksTex, previewAlpha);
|
|
}
|
|
|
|
for (int cy = 0; cy < 4; ++cy) {
|
|
for (int cx = 0; cx < 4; ++cx) {
|
|
if (Game::cellFilled(previewPiece, cx, cy)) {
|
|
float px = x + offsetX + cx * tileSize;
|
|
float py = y + offsetY + cy * tileSize;
|
|
drawBlockTexture(renderer, blocksTex, px, py, tileSize, pieceType);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset alpha
|
|
if (blocksTex) {
|
|
SDL_SetTextureAlphaMod(blocksTex, 255);
|
|
}
|
|
}
|
|
|
|
void GameRenderer::renderPlayingState(
|
|
SDL_Renderer* renderer,
|
|
Game* game,
|
|
FontAtlas* pixelFont,
|
|
LineEffect* lineEffect,
|
|
SDL_Texture* blocksTex,
|
|
float logicalW,
|
|
float logicalH,
|
|
float logicalScale,
|
|
float winW,
|
|
float winH,
|
|
bool showExitConfirmPopup,
|
|
int exitPopupSelectedButton,
|
|
bool suppressPauseVisuals
|
|
) {
|
|
if (!game || !pixelFont) return;
|
|
|
|
// Calculate actual content area (centered within the window)
|
|
float contentScale = logicalScale;
|
|
float contentW = logicalW * contentScale;
|
|
float contentH = logicalH * contentScale;
|
|
float contentOffsetX = (winW - contentW) * 0.5f / contentScale;
|
|
float contentOffsetY = (winH - contentH) * 0.5f / contentScale;
|
|
|
|
// Helper lambda for drawing rectangles with content offset
|
|
auto drawRectWithOffset = [&](float x, float y, float w, float h, SDL_Color c) {
|
|
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
|
SDL_FRect fr{x + contentOffsetX, y + contentOffsetY, w, h};
|
|
SDL_RenderFillRect(renderer, &fr);
|
|
};
|
|
|
|
// Responsive layout that scales with window size while maintaining margins
|
|
const float MIN_MARGIN = 40.0f;
|
|
const float TOP_MARGIN = 60.0f;
|
|
const float PANEL_WIDTH = 180.0f;
|
|
const float PANEL_SPACING = 30.0f;
|
|
const float NEXT_PIECE_HEIGHT = 120.0f;
|
|
const float BOTTOM_MARGIN = 60.0f;
|
|
|
|
// Calculate layout dimensions
|
|
const float availableWidth = logicalW - (MIN_MARGIN * 2) - (PANEL_WIDTH * 2) - (PANEL_SPACING * 2);
|
|
const float availableHeight = logicalH - TOP_MARGIN - BOTTOM_MARGIN - NEXT_PIECE_HEIGHT;
|
|
|
|
const float maxBlockSizeW = availableWidth / Game::COLS;
|
|
const float maxBlockSizeH = availableHeight / Game::ROWS;
|
|
const float BLOCK_SIZE = std::min(maxBlockSizeW, maxBlockSizeH);
|
|
const float finalBlockSize = std::max(20.0f, std::min(BLOCK_SIZE, 40.0f));
|
|
|
|
const float GRID_W = Game::COLS * finalBlockSize;
|
|
const float GRID_H = Game::ROWS * finalBlockSize;
|
|
|
|
// Calculate positions
|
|
const float totalContentHeight = NEXT_PIECE_HEIGHT + GRID_H;
|
|
const float availableVerticalSpace = logicalH - TOP_MARGIN - BOTTOM_MARGIN;
|
|
const float verticalCenterOffset = (availableVerticalSpace - totalContentHeight) * 0.5f;
|
|
const float contentStartY = TOP_MARGIN + verticalCenterOffset;
|
|
|
|
const float totalLayoutWidth = PANEL_WIDTH + PANEL_SPACING + GRID_W + PANEL_SPACING + PANEL_WIDTH;
|
|
const float layoutStartX = (logicalW - totalLayoutWidth) * 0.5f;
|
|
|
|
const float statsX = layoutStartX + contentOffsetX;
|
|
const float gridX = layoutStartX + PANEL_WIDTH + PANEL_SPACING + contentOffsetX;
|
|
const float scoreX = layoutStartX + PANEL_WIDTH + PANEL_SPACING + GRID_W + PANEL_SPACING + contentOffsetX;
|
|
const float gridY = contentStartY + NEXT_PIECE_HEIGHT + contentOffsetY;
|
|
|
|
const float statsY = gridY;
|
|
const float statsW = PANEL_WIDTH;
|
|
const float statsH = GRID_H;
|
|
|
|
// Next piece preview position
|
|
const float nextW = finalBlockSize * 4 + 20;
|
|
const float nextH = finalBlockSize * 2 + 20;
|
|
const float nextX = gridX + (GRID_W - nextW) * 0.5f;
|
|
const float nextY = contentStartY + contentOffsetY;
|
|
|
|
// Handle line clearing effects
|
|
if (game->hasCompletedLines() && lineEffect && !lineEffect->isActive()) {
|
|
auto completedLines = game->getCompletedLines();
|
|
lineEffect->startLineClear(completedLines, static_cast<int>(gridX), static_cast<int>(gridY), static_cast<int>(finalBlockSize));
|
|
}
|
|
|
|
// Draw game grid border
|
|
drawRectWithOffset(gridX - 3 - contentOffsetX, gridY - 3 - contentOffsetY, GRID_W + 6, GRID_H + 6, {100, 120, 200, 255});
|
|
drawRectWithOffset(gridX - 1 - contentOffsetX, gridY - 1 - contentOffsetY, GRID_W + 2, GRID_H + 2, {60, 80, 160, 255});
|
|
drawRectWithOffset(gridX - contentOffsetX, gridY - contentOffsetY, GRID_W, GRID_H, {20, 25, 35, 255});
|
|
|
|
// Draw panel backgrounds
|
|
SDL_SetRenderDrawColor(renderer, 10, 15, 25, 160);
|
|
SDL_FRect lbg{statsX - 16, gridY - 10, statsW + 32, GRID_H + 20};
|
|
SDL_RenderFillRect(renderer, &lbg);
|
|
|
|
SDL_FRect rbg{scoreX - 16, gridY - 16, statsW + 32, GRID_H + 32};
|
|
SDL_RenderFillRect(renderer, &rbg);
|
|
|
|
// Draw grid lines
|
|
SDL_SetRenderDrawColor(renderer, 40, 45, 60, 255);
|
|
for (int x = 1; x < Game::COLS; ++x) {
|
|
float lineX = gridX + x * finalBlockSize;
|
|
SDL_RenderLine(renderer, lineX, gridY, lineX, gridY + GRID_H);
|
|
}
|
|
for (int y = 1; y < Game::ROWS; ++y) {
|
|
float lineY = gridY + y * finalBlockSize;
|
|
SDL_RenderLine(renderer, gridX, lineY, gridX + GRID_W, lineY);
|
|
}
|
|
|
|
// Draw block statistics panel border
|
|
drawRectWithOffset(statsX - 3 - contentOffsetX, statsY - 3 - contentOffsetY, statsW + 6, statsH + 6, {100, 120, 200, 255});
|
|
drawRectWithOffset(statsX - contentOffsetX, statsY - contentOffsetY, statsW, statsH, {30, 35, 50, 255});
|
|
|
|
// Draw next piece preview panel border
|
|
drawRectWithOffset(nextX - 3 - contentOffsetX, nextY - 3 - contentOffsetY, nextW + 6, nextH + 6, {100, 120, 200, 255});
|
|
drawRectWithOffset(nextX - contentOffsetX, nextY - contentOffsetY, nextW, nextH, {30, 35, 50, 255});
|
|
|
|
// Draw the game board
|
|
const auto &board = game->boardRef();
|
|
for (int y = 0; y < Game::ROWS; ++y) {
|
|
for (int x = 0; x < Game::COLS; ++x) {
|
|
int v = board[y * Game::COLS + x];
|
|
if (v > 0) {
|
|
float bx = gridX + x * finalBlockSize;
|
|
float by = gridY + y * finalBlockSize;
|
|
drawBlockTexture(renderer, blocksTex, bx, by, finalBlockSize, v - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool allowActivePieceRender = !game->isPaused() || suppressPauseVisuals;
|
|
|
|
// Draw ghost piece (where current piece will land)
|
|
if (allowActivePieceRender) {
|
|
Game::Piece ghostPiece = game->current();
|
|
// Find landing position
|
|
while (true) {
|
|
Game::Piece testPiece = ghostPiece;
|
|
testPiece.y++;
|
|
bool collision = false;
|
|
|
|
// Simple collision check
|
|
for (int cy = 0; cy < 4; ++cy) {
|
|
for (int cx = 0; cx < 4; ++cx) {
|
|
if (Game::cellFilled(testPiece, cx, cy)) {
|
|
int gx = testPiece.x + cx;
|
|
int gy = testPiece.y + cy;
|
|
if (gy >= Game::ROWS || gx < 0 || gx >= Game::COLS ||
|
|
(gy >= 0 && board[gy * Game::COLS + gx] != 0)) {
|
|
collision = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (collision) break;
|
|
}
|
|
|
|
if (collision) break;
|
|
ghostPiece = testPiece;
|
|
}
|
|
|
|
// Draw ghost piece
|
|
drawPiece(renderer, blocksTex, ghostPiece, gridX, gridY, finalBlockSize, true);
|
|
}
|
|
|
|
// Draw the falling piece
|
|
if (allowActivePieceRender) {
|
|
drawPiece(renderer, blocksTex, game->current(), gridX, gridY, finalBlockSize, false);
|
|
}
|
|
|
|
// Draw line clearing effects
|
|
if (lineEffect && lineEffect->isActive()) {
|
|
lineEffect->render(renderer, blocksTex, static_cast<int>(gridX), static_cast<int>(gridY), static_cast<int>(finalBlockSize));
|
|
}
|
|
|
|
// Draw next piece preview
|
|
pixelFont->draw(renderer, nextX + 10, nextY - 20, "NEXT", 1.0f, {255, 220, 0, 255});
|
|
if (game->next().type < PIECE_COUNT) {
|
|
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->next().type), nextX + 10, nextY + 10, finalBlockSize * 0.6f);
|
|
}
|
|
|
|
// Draw block statistics (left panel)
|
|
pixelFont->draw(renderer, statsX + 10, statsY + 10, "BLOCKS", 1.0f, {255, 220, 0, 255});
|
|
|
|
const auto& blockCounts = game->getBlockCounts();
|
|
int totalBlocks = 0;
|
|
for (int i = 0; i < PIECE_COUNT; ++i) totalBlocks += blockCounts[i];
|
|
|
|
const float rowPadding = 18.0f;
|
|
const float rowWidth = statsW - rowPadding * 2.0f;
|
|
const float rowSpacing = 12.0f;
|
|
float yCursor = statsY + 44.0f;
|
|
|
|
for (int i = 0; i < PIECE_COUNT; ++i) {
|
|
float rowTop = yCursor;
|
|
float previewSize = finalBlockSize * 0.52f;
|
|
float previewX = statsX + rowPadding;
|
|
float previewY = rowTop - 14.0f;
|
|
|
|
// Determine actual preview height to keep bars below the blocks
|
|
Game::Piece previewPiece{};
|
|
previewPiece.type = static_cast<PieceType>(i);
|
|
int maxCy = -1;
|
|
for (int cy = 0; cy < 4; ++cy) {
|
|
for (int cx = 0; cx < 4; ++cx) {
|
|
if (Game::cellFilled(previewPiece, cx, cy)) {
|
|
maxCy = std::max(maxCy, cy);
|
|
}
|
|
}
|
|
}
|
|
float pieceHeight = (maxCy >= 0 ? maxCy + 1.0f : 1.0f) * previewSize;
|
|
|
|
int count = blockCounts[i];
|
|
char countStr[16];
|
|
snprintf(countStr, sizeof(countStr), "%d", count);
|
|
int countW = 0, countH = 0;
|
|
pixelFont->measure(countStr, 1.0f, countW, countH);
|
|
float countX = previewX + rowWidth - static_cast<float>(countW);
|
|
float countY = previewY + 9.0f;
|
|
|
|
int perc = (totalBlocks > 0) ? int(std::round(100.0 * double(count) / double(totalBlocks))) : 0;
|
|
char percStr[16];
|
|
snprintf(percStr, sizeof(percStr), "%d%%", perc);
|
|
|
|
float barX = previewX;
|
|
float barY = previewY + pieceHeight + 12.0f;
|
|
float barH = 6.0f;
|
|
float barW = rowWidth;
|
|
float percY = barY + barH + 8.0f;
|
|
|
|
float rowBottom = percY + 16.0f;
|
|
SDL_FRect rowBg{
|
|
previewX - 12.0f,
|
|
rowTop - 14.0f,
|
|
rowWidth + 24.0f,
|
|
rowBottom - (rowTop - 14.0f)
|
|
};
|
|
SDL_SetRenderDrawColor(renderer, 18, 26, 40, 200);
|
|
SDL_RenderFillRect(renderer, &rowBg);
|
|
SDL_SetRenderDrawColor(renderer, 70, 100, 150, 210);
|
|
SDL_RenderRect(renderer, &rowBg);
|
|
|
|
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(i), previewX, previewY, previewSize);
|
|
pixelFont->draw(renderer, countX, countY, countStr, 1.0f, {245, 245, 255, 255});
|
|
pixelFont->draw(renderer, previewX, percY, percStr, 0.8f, {215, 225, 240, 255});
|
|
|
|
SDL_SetRenderDrawColor(renderer, 110, 120, 140, 200);
|
|
SDL_FRect track{barX, barY, barW, barH};
|
|
SDL_RenderFillRect(renderer, &track);
|
|
SDL_Color pc = COLORS[i + 1];
|
|
SDL_SetRenderDrawColor(renderer, pc.r, pc.g, pc.b, 255);
|
|
float fillW = barW * (perc / 100.0f);
|
|
fillW = std::clamp(fillW, 0.0f, barW);
|
|
SDL_FRect fill{barX, barY, fillW, barH};
|
|
SDL_RenderFillRect(renderer, &fill);
|
|
|
|
yCursor = rowBottom + rowSpacing;
|
|
}
|
|
|
|
// Draw score panel (right side)
|
|
const float contentTopOffset = 0.0f;
|
|
const float contentBottomOffset = 290.0f;
|
|
const float contentPad = 36.0f;
|
|
float scoreContentH = (contentBottomOffset - contentTopOffset) + contentPad;
|
|
float baseY = gridY + (GRID_H - scoreContentH) * 0.5f;
|
|
|
|
pixelFont->draw(renderer, scoreX, baseY + 0, "SCORE", 1.0f, {255, 220, 0, 255});
|
|
char scoreStr[32];
|
|
snprintf(scoreStr, sizeof(scoreStr), "%d", game->score());
|
|
pixelFont->draw(renderer, scoreX, baseY + 25, scoreStr, 0.9f, {255, 255, 255, 255});
|
|
|
|
pixelFont->draw(renderer, scoreX, baseY + 70, "LINES", 1.0f, {255, 220, 0, 255});
|
|
char linesStr[16];
|
|
snprintf(linesStr, sizeof(linesStr), "%03d", game->lines());
|
|
pixelFont->draw(renderer, scoreX, baseY + 95, linesStr, 0.9f, {255, 255, 255, 255});
|
|
|
|
pixelFont->draw(renderer, scoreX, baseY + 140, "LEVEL", 1.0f, {255, 220, 0, 255});
|
|
char levelStr[16];
|
|
snprintf(levelStr, sizeof(levelStr), "%02d", game->level());
|
|
pixelFont->draw(renderer, scoreX, baseY + 165, levelStr, 0.9f, {255, 255, 255, 255});
|
|
|
|
// Next level progress
|
|
int startLv = game->startLevelBase();
|
|
int firstThreshold = (startLv + 1) * 10;
|
|
int linesDone = game->lines();
|
|
int nextThreshold = 0;
|
|
if (linesDone < firstThreshold) {
|
|
nextThreshold = firstThreshold;
|
|
} else {
|
|
int blocksPast = linesDone - firstThreshold;
|
|
nextThreshold = firstThreshold + ((blocksPast / 10) + 1) * 10;
|
|
}
|
|
int linesForNext = std::max(0, nextThreshold - linesDone);
|
|
pixelFont->draw(renderer, scoreX, baseY + 200, "NEXT LVL", 1.0f, {255, 220, 0, 255});
|
|
char nextStr[32];
|
|
snprintf(nextStr, sizeof(nextStr), "%d LINES", linesForNext);
|
|
pixelFont->draw(renderer, scoreX, baseY + 225, nextStr, 0.9f, {80, 255, 120, 255});
|
|
|
|
// Time display
|
|
pixelFont->draw(renderer, scoreX, baseY + 265, "TIME", 1.0f, {255, 220, 0, 255});
|
|
int totalSecs = static_cast<int>(game->elapsed());
|
|
int mins = totalSecs / 60;
|
|
int secs = totalSecs % 60;
|
|
char timeStr[16];
|
|
snprintf(timeStr, sizeof(timeStr), "%02d:%02d", mins, secs);
|
|
pixelFont->draw(renderer, scoreX, baseY + 290, timeStr, 0.9f, {255, 255, 255, 255});
|
|
|
|
// Gravity HUD
|
|
char gms[64];
|
|
double gms_val = game->getGravityMs();
|
|
double gfps = gms_val > 0.0 ? (1000.0 / gms_val) : 0.0;
|
|
snprintf(gms, sizeof(gms), "GRAV: %.0f ms (%.2f fps)", gms_val, gfps);
|
|
pixelFont->draw(renderer, logicalW - 260, 10, gms, 0.9f, {200, 200, 220, 255});
|
|
|
|
// Hold piece (if implemented)
|
|
if (game->held().type < PIECE_COUNT) {
|
|
pixelFont->draw(renderer, statsX + 10, statsY + statsH - 80, "HOLD", 1.0f, {255, 220, 0, 255});
|
|
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f);
|
|
}
|
|
|
|
// Pause overlay (skip when visuals are suppressed, e.g., countdown)
|
|
if (!suppressPauseVisuals && game->isPaused() && !showExitConfirmPopup) {
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
|
|
// Draw blur effect around the grid (keep in logical coordinates)
|
|
for (int i = -4; i <= 4; ++i) {
|
|
float spread = static_cast<float>(std::abs(i));
|
|
Uint8 alpha = Uint8(std::max(8.f, 32.f - spread * 4.f));
|
|
SDL_SetRenderDrawColor(renderer, 24, 32, 48, alpha);
|
|
SDL_FRect blurRect{
|
|
gridX - spread * 2.0f,
|
|
gridY - spread * 1.5f,
|
|
GRID_W + spread * 4.0f,
|
|
GRID_H + spread * 3.0f
|
|
};
|
|
SDL_RenderFillRect(renderer, &blurRect);
|
|
}
|
|
|
|
// Switch to window coordinates for the full-screen overlay and text
|
|
SDL_Rect oldViewport;
|
|
SDL_GetRenderViewport(renderer, &oldViewport);
|
|
float oldScaleX, oldScaleY;
|
|
SDL_GetRenderScale(renderer, &oldScaleX, &oldScaleY);
|
|
|
|
SDL_SetRenderViewport(renderer, nullptr);
|
|
SDL_SetRenderScale(renderer, 1.0f, 1.0f);
|
|
|
|
// Draw full screen overlay
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
|
|
SDL_FRect pauseOverlay{0, 0, winW, winH};
|
|
SDL_RenderFillRect(renderer, &pauseOverlay);
|
|
|
|
// Draw centered text
|
|
// Note: We multiply font scale by logicalScale to maintain consistent size
|
|
// since we reset the renderer scale to 1.0
|
|
const char* pausedText = "PAUSED";
|
|
float pausedScale = 2.0f * logicalScale;
|
|
int pW = 0, pH = 0;
|
|
pixelFont->measure(pausedText, pausedScale, pW, pH);
|
|
pixelFont->draw(renderer, (winW - pW) * 0.5f, (winH - pH) * 0.5f - (20 * logicalScale), pausedText, pausedScale, {255, 255, 255, 255});
|
|
|
|
const char* resumeText = "Press P to resume";
|
|
float resumeScale = 0.8f * logicalScale;
|
|
int rW = 0, rH = 0;
|
|
pixelFont->measure(resumeText, resumeScale, rW, rH);
|
|
pixelFont->draw(renderer, (winW - rW) * 0.5f, (winH - pH) * 0.5f + (40 * logicalScale), resumeText, resumeScale, {200, 200, 220, 255});
|
|
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
|
|
|
|
// Restore previous render state
|
|
SDL_SetRenderViewport(renderer, &oldViewport);
|
|
SDL_SetRenderScale(renderer, oldScaleX, oldScaleY);
|
|
}
|
|
|
|
// Exit confirmation popup styled like other retro panels
|
|
if (showExitConfirmPopup) {
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200);
|
|
SDL_FRect fullWin{0.f, 0.f, winW, winH};
|
|
SDL_RenderFillRect(renderer, &fullWin);
|
|
|
|
const float panelW = 640.0f;
|
|
const float panelH = 320.0f;
|
|
SDL_FRect panel{
|
|
(logicalW - panelW) * 0.5f + contentOffsetX,
|
|
(logicalH - panelH) * 0.5f + contentOffsetY,
|
|
panelW,
|
|
panelH
|
|
};
|
|
|
|
SDL_FRect shadow{panel.x + 6.0f, panel.y + 10.0f, panel.w, panel.h};
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
|
|
SDL_RenderFillRect(renderer, &shadow);
|
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
SDL_FRect glow{panel.x - float(i * 2), panel.y - float(i * 2), panel.w + float(i * 4), panel.h + float(i * 4)};
|
|
SDL_SetRenderDrawColor(renderer, 0, 180, 255, Uint8(44 - i * 7));
|
|
SDL_RenderRect(renderer, &glow);
|
|
}
|
|
|
|
SDL_SetRenderDrawColor(renderer, 18, 30, 52, 255);
|
|
SDL_RenderFillRect(renderer, &panel);
|
|
SDL_SetRenderDrawColor(renderer, 70, 120, 210, 255);
|
|
SDL_RenderRect(renderer, &panel);
|
|
|
|
SDL_FRect inner{panel.x + 24.0f, panel.y + 98.0f, panel.w - 48.0f, panel.h - 146.0f};
|
|
SDL_SetRenderDrawColor(renderer, 16, 24, 40, 235);
|
|
SDL_RenderFillRect(renderer, &inner);
|
|
SDL_SetRenderDrawColor(renderer, 40, 80, 140, 235);
|
|
SDL_RenderRect(renderer, &inner);
|
|
|
|
const std::string title = "EXIT GAME?";
|
|
int titleW = 0, titleH = 0;
|
|
const float titleScale = 1.8f;
|
|
pixelFont->measure(title, titleScale, titleW, titleH);
|
|
pixelFont->draw(renderer, panel.x + (panel.w - titleW) * 0.5f, panel.y + 30.0f, title, titleScale, {255, 230, 140, 255});
|
|
|
|
std::array<std::string, 2> lines = {
|
|
"Are you sure you want to quit?",
|
|
"Current progress will be lost."
|
|
};
|
|
float lineY = inner.y + 22.0f;
|
|
const float lineScale = 1.05f;
|
|
for (const auto& line : lines) {
|
|
int lineW = 0, lineH = 0;
|
|
pixelFont->measure(line, lineScale, lineW, lineH);
|
|
float textX = panel.x + (panel.w - lineW) * 0.5f;
|
|
pixelFont->draw(renderer, textX, lineY, line, lineScale, SDL_Color{210, 220, 240, 255});
|
|
lineY += lineH + 10.0f;
|
|
}
|
|
|
|
const float horizontalPad = 28.0f;
|
|
const float buttonGap = 32.0f;
|
|
const float buttonH = 66.0f;
|
|
float buttonW = (inner.w - horizontalPad * 2.0f - buttonGap) * 0.5f;
|
|
float buttonY = inner.y + inner.h - buttonH - 24.0f;
|
|
|
|
auto drawButton = [&](int idx, float x, const char* label) {
|
|
bool selected = (exitPopupSelectedButton == idx);
|
|
SDL_Color base = (idx == 0) ? SDL_Color{185, 70, 70, 255} : SDL_Color{60, 95, 150, 255};
|
|
SDL_Color body = selected ? SDL_Color{Uint8(std::min(255, base.r + 35)), Uint8(std::min(255, base.g + 35)), Uint8(std::min(255, base.b + 35)), 255} : base;
|
|
SDL_Color border = selected ? SDL_Color{255, 220, 120, 255} : SDL_Color{80, 110, 160, 255};
|
|
|
|
SDL_FRect btn{x, buttonY, buttonW, buttonH};
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120);
|
|
SDL_FRect btnShadow{btn.x + 4.0f, btn.y + 6.0f, btn.w, btn.h};
|
|
SDL_RenderFillRect(renderer, &btnShadow);
|
|
SDL_SetRenderDrawColor(renderer, body.r, body.g, body.b, body.a);
|
|
SDL_RenderFillRect(renderer, &btn);
|
|
SDL_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a);
|
|
SDL_RenderRect(renderer, &btn);
|
|
|
|
int textW = 0, textH = 0;
|
|
const float labelScale = 1.4f;
|
|
pixelFont->measure(label, labelScale, textW, textH);
|
|
float textX = btn.x + (btn.w - textW) * 0.5f;
|
|
float textY = btn.y + (btn.h - textH) * 0.5f;
|
|
SDL_Color textColor = selected ? SDL_Color{255, 255, 255, 255} : SDL_Color{230, 235, 250, 255};
|
|
pixelFont->draw(renderer, textX, textY, label, labelScale, textColor);
|
|
};
|
|
|
|
float yesX = inner.x + horizontalPad;
|
|
float noX = yesX + buttonW + buttonGap;
|
|
drawButton(0, yesX, "YES");
|
|
drawButton(1, noX, "NO");
|
|
}
|
|
}
|