added helper menu
This commit is contained in:
@ -45,6 +45,7 @@ add_executable(tetris
|
|||||||
src/graphics/effects/Starfield.cpp
|
src/graphics/effects/Starfield.cpp
|
||||||
src/graphics/effects/Starfield3D.cpp
|
src/graphics/effects/Starfield3D.cpp
|
||||||
src/graphics/ui/Font.cpp
|
src/graphics/ui/Font.cpp
|
||||||
|
src/graphics/ui/HelpOverlay.cpp
|
||||||
src/graphics/renderers/GameRenderer.cpp
|
src/graphics/renderers/GameRenderer.cpp
|
||||||
src/audio/Audio.cpp
|
src/audio/Audio.cpp
|
||||||
src/gameplay/effects/LineEffect.cpp
|
src/gameplay/effects/LineEffect.cpp
|
||||||
|
|||||||
@ -12,7 +12,7 @@ Sound=1
|
|||||||
SmoothScroll=1
|
SmoothScroll=1
|
||||||
|
|
||||||
[Player]
|
[Player]
|
||||||
Name=PLAYER
|
Name=GREGOR
|
||||||
|
|
||||||
[Debug]
|
[Debug]
|
||||||
Enabled=1
|
Enabled=1
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
#include "../GlobalState.h"
|
#include "../GlobalState.h"
|
||||||
#include "../../graphics/renderers/RenderManager.h"
|
#include "../../graphics/renderers/RenderManager.h"
|
||||||
#include "../../graphics/ui/Font.h"
|
#include "../../graphics/ui/Font.h"
|
||||||
|
#include "../../graphics/ui/HelpOverlay.h"
|
||||||
#include "../../graphics/effects/Starfield3D.h"
|
#include "../../graphics/effects/Starfield3D.h"
|
||||||
#include "../../graphics/effects/Starfield.h"
|
#include "../../graphics/effects/Starfield.h"
|
||||||
#include "../../graphics/renderers/GameRenderer.h"
|
#include "../../graphics/renderers/GameRenderer.h"
|
||||||
@ -351,9 +352,26 @@ bool ApplicationManager::initializeManagers() {
|
|||||||
consume = true;
|
consume = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// N: Play a test sound effect
|
if (!consume && sc == SDL_SCANCODE_H) {
|
||||||
if (!consume && sc == SDL_SCANCODE_N) {
|
AppState currentState = m_stateManager ? m_stateManager->getState() : AppState::Loading;
|
||||||
SoundEffectManager::instance().playSound("lets_go", 1.0f);
|
if (currentState != AppState::Loading) {
|
||||||
|
m_showHelpOverlay = !m_showHelpOverlay;
|
||||||
|
if (currentState == AppState::Playing && m_game) {
|
||||||
|
if (m_showHelpOverlay) {
|
||||||
|
if (!m_game->isPaused()) {
|
||||||
|
m_game->setPaused(true);
|
||||||
|
m_helpOverlayPausedGame = true;
|
||||||
|
} else {
|
||||||
|
m_helpOverlayPausedGame = false;
|
||||||
|
}
|
||||||
|
} else if (m_helpOverlayPausedGame) {
|
||||||
|
m_game->setPaused(false);
|
||||||
|
m_helpOverlayPausedGame = false;
|
||||||
|
}
|
||||||
|
} else if (!m_showHelpOverlay) {
|
||||||
|
m_helpOverlayPausedGame = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
consume = true;
|
consume = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,6 +388,7 @@ bool ApplicationManager::initializeManagers() {
|
|||||||
|
|
||||||
m_inputManager->registerMouseButtonHandler([this](int button, bool pressed, float x, float y){
|
m_inputManager->registerMouseButtonHandler([this](int button, bool pressed, float x, float y){
|
||||||
if (!m_stateManager) return;
|
if (!m_stateManager) return;
|
||||||
|
if (m_showHelpOverlay) return;
|
||||||
SDL_Event ev{};
|
SDL_Event ev{};
|
||||||
ev.type = pressed ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP;
|
ev.type = pressed ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP;
|
||||||
ev.button.button = button;
|
ev.button.button = button;
|
||||||
@ -380,6 +399,7 @@ bool ApplicationManager::initializeManagers() {
|
|||||||
|
|
||||||
m_inputManager->registerMouseMotionHandler([this](float x, float y, float dx, float dy){
|
m_inputManager->registerMouseMotionHandler([this](float x, float y, float dx, float dy){
|
||||||
if (!m_stateManager) return;
|
if (!m_stateManager) return;
|
||||||
|
if (m_showHelpOverlay) return;
|
||||||
SDL_Event ev{};
|
SDL_Event ev{};
|
||||||
ev.type = SDL_EVENT_MOUSE_MOTION;
|
ev.type = SDL_EVENT_MOUSE_MOTION;
|
||||||
ev.motion.x = int(x);
|
ev.motion.x = int(x);
|
||||||
@ -594,6 +614,7 @@ bool ApplicationManager::initializeGame() {
|
|||||||
m_stateContext.startLevelSelection = &m_startLevelSelection;
|
m_stateContext.startLevelSelection = &m_startLevelSelection;
|
||||||
m_stateContext.hoveredButton = &m_hoveredButton;
|
m_stateContext.hoveredButton = &m_hoveredButton;
|
||||||
m_stateContext.showSettingsPopup = &m_showSettingsPopup;
|
m_stateContext.showSettingsPopup = &m_showSettingsPopup;
|
||||||
|
m_stateContext.showHelpOverlay = &m_showHelpOverlay;
|
||||||
m_stateContext.showExitConfirmPopup = &m_showExitConfirmPopup;
|
m_stateContext.showExitConfirmPopup = &m_showExitConfirmPopup;
|
||||||
m_stateContext.exitPopupSelectedButton = &m_exitPopupSelectedButton;
|
m_stateContext.exitPopupSelectedButton = &m_exitPopupSelectedButton;
|
||||||
m_stateContext.playerName = &m_playerName;
|
m_stateContext.playerName = &m_playerName;
|
||||||
@ -1336,6 +1357,41 @@ void ApplicationManager::setupStateHandlers() {
|
|||||||
m_stateManager->registerRenderHandler(AppState::Playing, debugOverlay);
|
m_stateManager->registerRenderHandler(AppState::Playing, debugOverlay);
|
||||||
m_stateManager->registerRenderHandler(AppState::GameOver, debugOverlay);
|
m_stateManager->registerRenderHandler(AppState::GameOver, debugOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto helpOverlayRender = [this](RenderManager& renderer) {
|
||||||
|
if (!m_showHelpOverlay || !m_stateContext.pixelFont) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Renderer* sdlRenderer = renderer.getSDLRenderer();
|
||||||
|
if (!sdlRenderer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Rect logicalVP = renderer.getLogicalViewport();
|
||||||
|
float logicalScale = renderer.getLogicalScale();
|
||||||
|
SDL_SetRenderViewport(sdlRenderer, &logicalVP);
|
||||||
|
SDL_SetRenderScale(sdlRenderer, logicalScale, logicalScale);
|
||||||
|
HelpOverlay::Render(
|
||||||
|
sdlRenderer,
|
||||||
|
*m_stateContext.pixelFont,
|
||||||
|
static_cast<float>(Config::Logical::WIDTH),
|
||||||
|
static_cast<float>(Config::Logical::HEIGHT),
|
||||||
|
0.0f,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
|
SDL_SetRenderViewport(sdlRenderer, nullptr);
|
||||||
|
SDL_SetRenderScale(sdlRenderer, 1.0f, 1.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (m_stateManager) {
|
||||||
|
m_stateManager->registerRenderHandler(AppState::Loading, helpOverlayRender);
|
||||||
|
m_stateManager->registerRenderHandler(AppState::Menu, helpOverlayRender);
|
||||||
|
m_stateManager->registerRenderHandler(AppState::Options, helpOverlayRender);
|
||||||
|
m_stateManager->registerRenderHandler(AppState::LevelSelector, helpOverlayRender);
|
||||||
|
m_stateManager->registerRenderHandler(AppState::Playing, helpOverlayRender);
|
||||||
|
m_stateManager->registerRenderHandler(AppState::GameOver, helpOverlayRender);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplicationManager::processEvents() {
|
void ApplicationManager::processEvents() {
|
||||||
|
|||||||
@ -94,6 +94,7 @@ private:
|
|||||||
int m_startLevelSelection = 0;
|
int m_startLevelSelection = 0;
|
||||||
int m_hoveredButton = -1;
|
int m_hoveredButton = -1;
|
||||||
bool m_showSettingsPopup = false;
|
bool m_showSettingsPopup = false;
|
||||||
|
bool m_showHelpOverlay = false;
|
||||||
bool m_showExitConfirmPopup = false;
|
bool m_showExitConfirmPopup = false;
|
||||||
int m_exitPopupSelectedButton = 1; // 0 = YES, 1 = NO
|
int m_exitPopupSelectedButton = 1; // 0 = YES, 1 = NO
|
||||||
bool m_isFullscreen = false;
|
bool m_isFullscreen = false;
|
||||||
@ -140,6 +141,7 @@ private:
|
|||||||
|
|
||||||
// Animation state
|
// Animation state
|
||||||
float m_logoAnimCounter = 0.0f;
|
float m_logoAnimCounter = 0.0f;
|
||||||
|
bool m_helpOverlayPausedGame = false;
|
||||||
|
|
||||||
// Gameplay background (per-level) with fade, mirroring main.cpp behavior
|
// Gameplay background (per-level) with fade, mirroring main.cpp behavior
|
||||||
SDL_Texture* m_levelBackgroundTex = nullptr;
|
SDL_Texture* m_levelBackgroundTex = nullptr;
|
||||||
|
|||||||
133
src/graphics/ui/HelpOverlay.cpp
Normal file
133
src/graphics/ui/HelpOverlay.cpp
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#include "HelpOverlay.h"
|
||||||
|
#include "Font.h"
|
||||||
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct ShortcutEntry {
|
||||||
|
const char* combo;
|
||||||
|
const char* description;
|
||||||
|
};
|
||||||
|
|
||||||
|
void drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color color) {
|
||||||
|
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||||
|
SDL_FRect rect{x, y, w, h};
|
||||||
|
SDL_RenderFillRect(renderer, &rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fitScale(FontAtlas& font, const char* text, float initialScale, float maxWidth) {
|
||||||
|
int textW = 0, textH = 0;
|
||||||
|
float scale = initialScale;
|
||||||
|
font.measure(text, scale, textW, textH);
|
||||||
|
if (textW > maxWidth && textW > 0) {
|
||||||
|
float factor = maxWidth / static_cast<float>(textW);
|
||||||
|
scale = std::max(0.65f, scale * factor);
|
||||||
|
}
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace HelpOverlay {
|
||||||
|
|
||||||
|
void Render(
|
||||||
|
SDL_Renderer* renderer,
|
||||||
|
FontAtlas& font,
|
||||||
|
float logicalWidth,
|
||||||
|
float logicalHeight,
|
||||||
|
float offsetX,
|
||||||
|
float offsetY) {
|
||||||
|
if (!renderer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::array<ShortcutEntry, 5> generalShortcuts{{
|
||||||
|
{"H", "Toggle this help overlay"},
|
||||||
|
{"ESC", "Back / cancel current popup"},
|
||||||
|
{"F11 or ALT+ENTER", "Toggle fullscreen"},
|
||||||
|
{"M", "Mute or unmute music"},
|
||||||
|
{"S", "Toggle sound effects"}
|
||||||
|
}};
|
||||||
|
|
||||||
|
const std::array<ShortcutEntry, 2> menuShortcuts{{
|
||||||
|
{"ARROW KEYS", "Navigate menu buttons"},
|
||||||
|
{"ENTER / SPACE", "Activate highlighted action"}
|
||||||
|
}};
|
||||||
|
|
||||||
|
const std::array<ShortcutEntry, 7> gameplayShortcuts{{
|
||||||
|
{"LEFT / RIGHT", "Move active piece"},
|
||||||
|
{"DOWN", "Soft drop (faster fall)"},
|
||||||
|
{"SPACE", "Hard drop / instant lock"},
|
||||||
|
{"UP", "Rotate clockwise"},
|
||||||
|
{"X", "Rotate counter-clockwise"},
|
||||||
|
{"P", "Pause or resume"},
|
||||||
|
{"ESC", "Open exit confirmation"}
|
||||||
|
}};
|
||||||
|
|
||||||
|
const float boxW = logicalWidth * 0.7f;
|
||||||
|
const float boxH = logicalHeight * 0.55f;
|
||||||
|
const float boxX = offsetX + (logicalWidth - boxW) * 0.5f;
|
||||||
|
const float boxY = offsetY + (logicalHeight - boxH) * 0.5f;
|
||||||
|
|
||||||
|
drawRect(renderer, boxX - 6.0f, boxY - 6.0f, boxW + 12.0f, boxH + 12.0f, {70, 90, 150, 255});
|
||||||
|
drawRect(renderer, boxX - 2.0f, boxY - 2.0f, boxW + 4.0f, boxH + 4.0f, {10, 12, 20, 255});
|
||||||
|
drawRect(renderer, boxX, boxY, boxW, boxH, {18, 22, 35, 240});
|
||||||
|
|
||||||
|
const float titleScale = 1.7f;
|
||||||
|
font.draw(renderer, boxX + 28.0f, boxY + 24.0f, "HELP & SHORTCUTS", titleScale, {255, 220, 0, 255});
|
||||||
|
|
||||||
|
const float contentPadding = 32.0f;
|
||||||
|
const float columnGap = 30.0f;
|
||||||
|
const float columnWidth = (boxW - contentPadding * 2.0f - columnGap) * 0.5f;
|
||||||
|
const float leftColumnX = boxX + contentPadding;
|
||||||
|
const float rightColumnX = leftColumnX + columnWidth + columnGap;
|
||||||
|
const float footerHeight = 46.0f;
|
||||||
|
const float footerPadding = 18.0f;
|
||||||
|
|
||||||
|
const float sectionTitleScale = 1.1f;
|
||||||
|
const float comboScale = 0.92f;
|
||||||
|
const float descBaseScale = 0.8f;
|
||||||
|
const float comboSpacing = 22.0f;
|
||||||
|
const float sectionSpacing = 14.0f;
|
||||||
|
|
||||||
|
auto drawSection = [&](float startX, float& cursorY, const char* title, const auto& entries) {
|
||||||
|
font.draw(renderer, startX, cursorY, title, sectionTitleScale, {180, 200, 255, 255});
|
||||||
|
cursorY += 26.0f;
|
||||||
|
for (const auto& entry : entries) {
|
||||||
|
font.draw(renderer, startX, cursorY, entry.combo, comboScale, {255, 255, 255, 255});
|
||||||
|
cursorY += comboSpacing;
|
||||||
|
|
||||||
|
float descScale = fitScale(font, entry.description, descBaseScale, columnWidth - 10.0f);
|
||||||
|
font.draw(renderer, startX, cursorY, entry.description, descScale, {200, 210, 230, 255});
|
||||||
|
int descW = 0, descH = 0;
|
||||||
|
font.measure(entry.description, descScale, descW, descH);
|
||||||
|
cursorY += static_cast<float>(descH) + 10.0f;
|
||||||
|
}
|
||||||
|
cursorY += sectionSpacing;
|
||||||
|
};
|
||||||
|
|
||||||
|
float leftCursorY = boxY + 80.0f;
|
||||||
|
float rightCursorY = boxY + 80.0f;
|
||||||
|
drawSection(leftColumnX, leftCursorY, "GENERAL", generalShortcuts);
|
||||||
|
drawSection(leftColumnX, leftCursorY, "MENUS", menuShortcuts);
|
||||||
|
drawSection(rightColumnX, rightCursorY, "GAMEPLAY", gameplayShortcuts);
|
||||||
|
|
||||||
|
SDL_FRect footerRect{
|
||||||
|
boxX + contentPadding,
|
||||||
|
boxY + boxH - contentPadding - footerHeight,
|
||||||
|
boxW - contentPadding * 2.0f,
|
||||||
|
footerHeight
|
||||||
|
};
|
||||||
|
drawRect(renderer, footerRect.x, footerRect.y, footerRect.w, footerRect.h, {24, 30, 50, 255});
|
||||||
|
SDL_SetRenderDrawColor(renderer, 90, 110, 170, 255);
|
||||||
|
SDL_RenderRect(renderer, &footerRect);
|
||||||
|
|
||||||
|
const char* closeLabel = "PRESS H TO CLOSE";
|
||||||
|
float closeScale = fitScale(font, closeLabel, 1.0f, footerRect.w - footerPadding * 2.0f);
|
||||||
|
int closeW = 0, closeH = 0;
|
||||||
|
font.measure(closeLabel, closeScale, closeW, closeH);
|
||||||
|
float closeX = footerRect.x + (footerRect.w - static_cast<float>(closeW)) * 0.5f;
|
||||||
|
float closeY = footerRect.y + (footerRect.h - static_cast<float>(closeH)) * 0.5f;
|
||||||
|
font.draw(renderer, closeX, closeY, closeLabel, closeScale, {215, 220, 240, 255});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HelpOverlay
|
||||||
16
src/graphics/ui/HelpOverlay.h
Normal file
16
src/graphics/ui/HelpOverlay.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
class FontAtlas;
|
||||||
|
|
||||||
|
namespace HelpOverlay {
|
||||||
|
// Draws the help popup contents inside the logical coordinate space.
|
||||||
|
// Optional offsets allow aligning the box when the logical canvas is letterboxed.
|
||||||
|
void Render(
|
||||||
|
SDL_Renderer* renderer,
|
||||||
|
FontAtlas& font,
|
||||||
|
float logicalWidth,
|
||||||
|
float logicalHeight,
|
||||||
|
float offsetX = 0.0f,
|
||||||
|
float offsetY = 0.0f);
|
||||||
|
}
|
||||||
72
src/main.cpp
72
src/main.cpp
@ -23,6 +23,7 @@
|
|||||||
#include "graphics/effects/Starfield.h"
|
#include "graphics/effects/Starfield.h"
|
||||||
#include "graphics/effects/Starfield3D.h"
|
#include "graphics/effects/Starfield3D.h"
|
||||||
#include "graphics/ui/Font.h"
|
#include "graphics/ui/Font.h"
|
||||||
|
#include "graphics/ui/HelpOverlay.h"
|
||||||
#include "gameplay/effects/LineEffect.h"
|
#include "gameplay/effects/LineEffect.h"
|
||||||
#include "states/State.h"
|
#include "states/State.h"
|
||||||
#include "states/LoadingState.h"
|
#include "states/LoadingState.h"
|
||||||
@ -487,10 +488,10 @@ static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musi
|
|||||||
// Instructions
|
// Instructions
|
||||||
font.draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, {200, 200, 220, 255});
|
font.draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, {200, 200, 220, 255});
|
||||||
font.draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, {200, 200, 220, 255});
|
font.draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, {200, 200, 220, 255});
|
||||||
font.draw(renderer, popupX + 20, popupY + 190, "N = PLAY LETS_GO", 1.0f, {200, 200, 220, 255});
|
font.draw(renderer, popupX + 20, popupY + 190, "ESC = CLOSE", 1.0f, {200, 200, 220, 255});
|
||||||
font.draw(renderer, popupX + 20, popupY + 210, "ESC = CLOSE", 1.0f, {200, 200, 220, 255});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Starfield effect for background
|
// Starfield effect for background
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -504,12 +505,14 @@ static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musi
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
static double logoAnimCounter = 0.0;
|
static double logoAnimCounter = 0.0;
|
||||||
static bool showSettingsPopup = false;
|
static bool showSettingsPopup = false;
|
||||||
|
static bool showHelpOverlay = false;
|
||||||
static bool showExitConfirmPopup = false;
|
static bool showExitConfirmPopup = false;
|
||||||
static int exitPopupSelectedButton = 1; // 0 = YES, 1 = NO
|
static int exitPopupSelectedButton = 1; // 0 = YES, 1 = NO
|
||||||
static bool musicEnabled = true;
|
static bool musicEnabled = true;
|
||||||
static int hoveredButton = -1; // -1 = none, 0 = play, 1 = level, 2 = settings
|
static int hoveredButton = -1; // -1 = none, 0 = play, 1 = level, 2 = settings
|
||||||
static bool isNewHighScore = false;
|
static bool isNewHighScore = false;
|
||||||
static std::string playerName = "";
|
static std::string playerName = "";
|
||||||
|
static bool helpOverlayPausedGame = false;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Tetris Block Fireworks for intro animation (block particles)
|
// Tetris Block Fireworks for intro animation (block particles)
|
||||||
@ -904,6 +907,7 @@ int main(int, char **)
|
|||||||
ctx.startLevelSelection = &startLevelSelection;
|
ctx.startLevelSelection = &startLevelSelection;
|
||||||
ctx.hoveredButton = &hoveredButton;
|
ctx.hoveredButton = &hoveredButton;
|
||||||
ctx.showSettingsPopup = &showSettingsPopup;
|
ctx.showSettingsPopup = &showSettingsPopup;
|
||||||
|
ctx.showHelpOverlay = &showHelpOverlay;
|
||||||
ctx.showExitConfirmPopup = &showExitConfirmPopup;
|
ctx.showExitConfirmPopup = &showExitConfirmPopup;
|
||||||
ctx.exitPopupSelectedButton = &exitPopupSelectedButton;
|
ctx.exitPopupSelectedButton = &exitPopupSelectedButton;
|
||||||
ctx.gameplayCountdownActive = &gameplayCountdownActive;
|
ctx.gameplayCountdownActive = &gameplayCountdownActive;
|
||||||
@ -1027,12 +1031,22 @@ int main(int, char **)
|
|||||||
running = false;
|
running = false;
|
||||||
else {
|
else {
|
||||||
// Route event to state manager handlers for per-state logic
|
// Route event to state manager handlers for per-state logic
|
||||||
stateMgr.handleEvent(e);
|
const bool isUserInputEvent =
|
||||||
// Keep the local `state` variable in sync with StateManager in case
|
e.type == SDL_EVENT_KEY_DOWN ||
|
||||||
// a state handler requested a transition (handlers may call
|
e.type == SDL_EVENT_KEY_UP ||
|
||||||
// stateMgr.setState()). Many branches below rely on the local
|
e.type == SDL_EVENT_TEXT_INPUT ||
|
||||||
// `state` variable, so update it immediately after handling.
|
e.type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
|
||||||
state = stateMgr.getState();
|
e.type == SDL_EVENT_MOUSE_BUTTON_UP ||
|
||||||
|
e.type == SDL_EVENT_MOUSE_MOTION;
|
||||||
|
|
||||||
|
if (!(showHelpOverlay && isUserInputEvent)) {
|
||||||
|
stateMgr.handleEvent(e);
|
||||||
|
// Keep the local `state` variable in sync with StateManager in case
|
||||||
|
// a state handler requested a transition (handlers may call
|
||||||
|
// stateMgr.setState()). Many branches below rely on the local
|
||||||
|
// `state` variable, so update it immediately after handling.
|
||||||
|
state = stateMgr.getState();
|
||||||
|
}
|
||||||
|
|
||||||
// Global key toggles (applies regardless of state)
|
// Global key toggles (applies regardless of state)
|
||||||
if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
||||||
@ -1048,11 +1062,23 @@ int main(int, char **)
|
|||||||
SoundEffectManager::instance().setEnabled(!SoundEffectManager::instance().isEnabled());
|
SoundEffectManager::instance().setEnabled(!SoundEffectManager::instance().isEnabled());
|
||||||
Settings::instance().setSoundEnabled(SoundEffectManager::instance().isEnabled());
|
Settings::instance().setSoundEnabled(SoundEffectManager::instance().isEnabled());
|
||||||
}
|
}
|
||||||
if (e.key.scancode == SDL_SCANCODE_N)
|
if (e.key.scancode == SDL_SCANCODE_H && state != AppState::Loading)
|
||||||
{
|
{
|
||||||
// Manually trigger a random voice line for quick testing
|
showHelpOverlay = !showHelpOverlay;
|
||||||
if (!allVoiceSounds.empty()) {
|
if (state == AppState::Playing) {
|
||||||
SoundEffectManager::instance().playRandomSound(allVoiceSounds, 1.0f);
|
if (showHelpOverlay) {
|
||||||
|
if (!game.isPaused()) {
|
||||||
|
game.setPaused(true);
|
||||||
|
helpOverlayPausedGame = true;
|
||||||
|
} else {
|
||||||
|
helpOverlayPausedGame = false;
|
||||||
|
}
|
||||||
|
} else if (helpOverlayPausedGame) {
|
||||||
|
game.setPaused(false);
|
||||||
|
helpOverlayPausedGame = false;
|
||||||
|
}
|
||||||
|
} else if (!showHelpOverlay) {
|
||||||
|
helpOverlayPausedGame = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.key.key == SDLK_F11 || (e.key.key == SDLK_RETURN && (e.key.mod & SDL_KMOD_ALT)))
|
if (e.key.key == SDLK_F11 || (e.key.key == SDLK_RETURN && (e.key.mod & SDL_KMOD_ALT)))
|
||||||
@ -1064,13 +1090,13 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Text input for high score
|
// Text input for high score
|
||||||
if (state == AppState::GameOver && isNewHighScore && e.type == SDL_EVENT_TEXT_INPUT) {
|
if (!showHelpOverlay && state == AppState::GameOver && isNewHighScore && e.type == SDL_EVENT_TEXT_INPUT) {
|
||||||
if (playerName.length() < 12) {
|
if (playerName.length() < 12) {
|
||||||
playerName += e.text.text;
|
playerName += e.text.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == AppState::GameOver && e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
if (!showHelpOverlay && state == AppState::GameOver && e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
||||||
if (isNewHighScore) {
|
if (isNewHighScore) {
|
||||||
if (e.key.scancode == SDL_SCANCODE_BACKSPACE && !playerName.empty()) {
|
if (e.key.scancode == SDL_SCANCODE_BACKSPACE && !playerName.empty()) {
|
||||||
playerName.pop_back();
|
playerName.pop_back();
|
||||||
@ -1096,7 +1122,7 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mouse handling remains in main loop for UI interactions
|
// Mouse handling remains in main loop for UI interactions
|
||||||
if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
|
if (!showHelpOverlay && e.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
|
||||||
{
|
{
|
||||||
float mx = (float)e.button.x, my = (float)e.button.y;
|
float mx = (float)e.button.x, my = (float)e.button.y;
|
||||||
if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h)
|
if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h)
|
||||||
@ -1227,7 +1253,7 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e.type == SDL_EVENT_MOUSE_MOTION)
|
else if (!showHelpOverlay && e.type == SDL_EVENT_MOUSE_MOTION)
|
||||||
{
|
{
|
||||||
float mx = (float)e.motion.x, my = (float)e.motion.y;
|
float mx = (float)e.motion.x, my = (float)e.motion.y;
|
||||||
if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h)
|
if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h)
|
||||||
@ -1886,6 +1912,20 @@ int main(int, char **)
|
|||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showHelpOverlay) {
|
||||||
|
SDL_SetRenderViewport(renderer, &logicalVP);
|
||||||
|
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
|
||||||
|
float contentOffsetX = 0.0f;
|
||||||
|
float contentOffsetY = 0.0f;
|
||||||
|
if (logicalScale > 0.0f) {
|
||||||
|
float scaledW = LOGICAL_W * logicalScale;
|
||||||
|
float scaledH = LOGICAL_H * logicalScale;
|
||||||
|
contentOffsetX = (winW - scaledW) * 0.5f / logicalScale;
|
||||||
|
contentOffsetY = (winH - scaledH) * 0.5f / logicalScale;
|
||||||
|
}
|
||||||
|
HelpOverlay::Render(renderer, pixelFont, LOGICAL_W, LOGICAL_H, contentOffsetX, contentOffsetY);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -465,8 +465,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
ctx.font->draw(renderer, popupX + 140, popupY + 100, "ON", 1.5f, SDL_Color{0,255,0,255});
|
ctx.font->draw(renderer, popupX + 140, popupY + 100, "ON", 1.5f, SDL_Color{0,255,0,255});
|
||||||
ctx.font->draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, SDL_Color{200,200,220,255});
|
ctx.font->draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, SDL_Color{200,200,220,255});
|
||||||
ctx.font->draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, SDL_Color{200,200,220,255});
|
ctx.font->draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, SDL_Color{200,200,220,255});
|
||||||
ctx.font->draw(renderer, popupX + 20, popupY + 190, "N = PLAY LETS_GO", 1.0f, SDL_Color{200,200,220,255});
|
ctx.font->draw(renderer, popupX + 20, popupY + 190, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255});
|
||||||
ctx.font->draw(renderer, popupX + 20, popupY + 210, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255});
|
|
||||||
}
|
}
|
||||||
// Trace exit
|
// Trace exit
|
||||||
{
|
{
|
||||||
|
|||||||
@ -104,8 +104,7 @@ void PlayingState::handleEvent(const SDL_Event& e) {
|
|||||||
// Tetris controls (only when not paused)
|
// Tetris controls (only when not paused)
|
||||||
if (!ctx.game->isPaused()) {
|
if (!ctx.game->isPaused()) {
|
||||||
// Rotation (still event-based for precise timing)
|
// Rotation (still event-based for precise timing)
|
||||||
if (e.key.scancode == SDL_SCANCODE_UP || e.key.scancode == SDL_SCANCODE_W ||
|
if (e.key.scancode == SDL_SCANCODE_UP) {
|
||||||
e.key.scancode == SDL_SCANCODE_Z) {
|
|
||||||
ctx.game->rotate(1); // Clockwise rotation
|
ctx.game->rotate(1); // Clockwise rotation
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,7 @@ struct StateContext {
|
|||||||
int* hoveredButton = nullptr;
|
int* hoveredButton = nullptr;
|
||||||
// Menu popups (exposed from main)
|
// Menu popups (exposed from main)
|
||||||
bool* showSettingsPopup = nullptr;
|
bool* showSettingsPopup = nullptr;
|
||||||
|
bool* showHelpOverlay = nullptr;
|
||||||
bool* showExitConfirmPopup = nullptr; // If true, show "Exit game?" confirmation while playing
|
bool* showExitConfirmPopup = nullptr; // If true, show "Exit game?" confirmation while playing
|
||||||
int* exitPopupSelectedButton = nullptr; // 0 = YES, 1 = NO (default)
|
int* exitPopupSelectedButton = nullptr; // 0 = YES, 1 = NO (default)
|
||||||
bool* gameplayCountdownActive = nullptr; // True if start-of-game countdown is running
|
bool* gameplayCountdownActive = nullptr; // True if start-of-game countdown is running
|
||||||
|
|||||||
@ -83,6 +83,5 @@ void menu_drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicE
|
|||||||
font.draw(renderer, popupX + 140, popupY + 100, sfxOn ? "ON" : "OFF", 1.5f, sfxOn ? SDL_Color{0,255,0,255} : SDL_Color{255,0,0,255});
|
font.draw(renderer, popupX + 140, popupY + 100, sfxOn ? "ON" : "OFF", 1.5f, sfxOn ? SDL_Color{0,255,0,255} : SDL_Color{255,0,0,255});
|
||||||
font.draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, SDL_Color{200,200,220,255});
|
font.draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, SDL_Color{200,200,220,255});
|
||||||
font.draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, SDL_Color{200,200,220,255});
|
font.draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, SDL_Color{200,200,220,255});
|
||||||
font.draw(renderer, popupX + 20, popupY + 190, "N = PLAY LETS_GO", 1.0f, SDL_Color{200,200,220,255});
|
font.draw(renderer, popupX + 20, popupY + 190, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255});
|
||||||
font.draw(renderer, popupX + 20, popupY + 210, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user