From 55c40f0516b530abad0c8304610cbc7e8dada896 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sun, 30 Nov 2025 13:30:02 +0100 Subject: [PATCH] added helper menu --- CMakeLists.txt | 1 + settings.ini | 2 +- src/core/application/ApplicationManager.cpp | 62 ++++++++- src/core/application/ApplicationManager.h | 2 + src/graphics/ui/HelpOverlay.cpp | 133 ++++++++++++++++++++ src/graphics/ui/HelpOverlay.h | 16 +++ src/main.cpp | 72 ++++++++--- src/states/MenuState.cpp | 3 +- src/states/PlayingState.cpp | 3 +- src/states/State.h | 1 + src/ui/MenuWrappers.cpp | 3 +- 11 files changed, 272 insertions(+), 26 deletions(-) create mode 100644 src/graphics/ui/HelpOverlay.cpp create mode 100644 src/graphics/ui/HelpOverlay.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 18b9efc..b530b2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(tetris src/graphics/effects/Starfield.cpp src/graphics/effects/Starfield3D.cpp src/graphics/ui/Font.cpp + src/graphics/ui/HelpOverlay.cpp src/graphics/renderers/GameRenderer.cpp src/audio/Audio.cpp src/gameplay/effects/LineEffect.cpp diff --git a/settings.ini b/settings.ini index 384ce2a..200523e 100644 --- a/settings.ini +++ b/settings.ini @@ -12,7 +12,7 @@ Sound=1 SmoothScroll=1 [Player] -Name=PLAYER +Name=GREGOR [Debug] Enabled=1 diff --git a/src/core/application/ApplicationManager.cpp b/src/core/application/ApplicationManager.cpp index fd282d0..c50bdd4 100644 --- a/src/core/application/ApplicationManager.cpp +++ b/src/core/application/ApplicationManager.cpp @@ -20,6 +20,7 @@ #include "../GlobalState.h" #include "../../graphics/renderers/RenderManager.h" #include "../../graphics/ui/Font.h" +#include "../../graphics/ui/HelpOverlay.h" #include "../../graphics/effects/Starfield3D.h" #include "../../graphics/effects/Starfield.h" #include "../../graphics/renderers/GameRenderer.h" @@ -351,9 +352,26 @@ bool ApplicationManager::initializeManagers() { consume = true; } - // N: Play a test sound effect - if (!consume && sc == SDL_SCANCODE_N) { - SoundEffectManager::instance().playSound("lets_go", 1.0f); + if (!consume && sc == SDL_SCANCODE_H) { + AppState currentState = m_stateManager ? m_stateManager->getState() : AppState::Loading; + 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; } } @@ -370,6 +388,7 @@ bool ApplicationManager::initializeManagers() { m_inputManager->registerMouseButtonHandler([this](int button, bool pressed, float x, float y){ if (!m_stateManager) return; + if (m_showHelpOverlay) return; SDL_Event ev{}; ev.type = pressed ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; ev.button.button = button; @@ -380,6 +399,7 @@ bool ApplicationManager::initializeManagers() { m_inputManager->registerMouseMotionHandler([this](float x, float y, float dx, float dy){ if (!m_stateManager) return; + if (m_showHelpOverlay) return; SDL_Event ev{}; ev.type = SDL_EVENT_MOUSE_MOTION; ev.motion.x = int(x); @@ -594,6 +614,7 @@ bool ApplicationManager::initializeGame() { m_stateContext.startLevelSelection = &m_startLevelSelection; m_stateContext.hoveredButton = &m_hoveredButton; m_stateContext.showSettingsPopup = &m_showSettingsPopup; + m_stateContext.showHelpOverlay = &m_showHelpOverlay; m_stateContext.showExitConfirmPopup = &m_showExitConfirmPopup; m_stateContext.exitPopupSelectedButton = &m_exitPopupSelectedButton; m_stateContext.playerName = &m_playerName; @@ -1336,6 +1357,41 @@ void ApplicationManager::setupStateHandlers() { m_stateManager->registerRenderHandler(AppState::Playing, 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(Config::Logical::WIDTH), + static_cast(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() { diff --git a/src/core/application/ApplicationManager.h b/src/core/application/ApplicationManager.h index 386f31d..220b2a5 100644 --- a/src/core/application/ApplicationManager.h +++ b/src/core/application/ApplicationManager.h @@ -94,6 +94,7 @@ private: int m_startLevelSelection = 0; int m_hoveredButton = -1; bool m_showSettingsPopup = false; + bool m_showHelpOverlay = false; bool m_showExitConfirmPopup = false; int m_exitPopupSelectedButton = 1; // 0 = YES, 1 = NO bool m_isFullscreen = false; @@ -140,6 +141,7 @@ private: // Animation state float m_logoAnimCounter = 0.0f; + bool m_helpOverlayPausedGame = false; // Gameplay background (per-level) with fade, mirroring main.cpp behavior SDL_Texture* m_levelBackgroundTex = nullptr; diff --git a/src/graphics/ui/HelpOverlay.cpp b/src/graphics/ui/HelpOverlay.cpp new file mode 100644 index 0000000..b1469d6 --- /dev/null +++ b/src/graphics/ui/HelpOverlay.cpp @@ -0,0 +1,133 @@ +#include "HelpOverlay.h" +#include "Font.h" +#include +#include + +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(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 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 menuShortcuts{{ + {"ARROW KEYS", "Navigate menu buttons"}, + {"ENTER / SPACE", "Activate highlighted action"} + }}; + + const std::array 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(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(closeW)) * 0.5f; + float closeY = footerRect.y + (footerRect.h - static_cast(closeH)) * 0.5f; + font.draw(renderer, closeX, closeY, closeLabel, closeScale, {215, 220, 240, 255}); +} + +} // namespace HelpOverlay diff --git a/src/graphics/ui/HelpOverlay.h b/src/graphics/ui/HelpOverlay.h new file mode 100644 index 0000000..1de9b68 --- /dev/null +++ b/src/graphics/ui/HelpOverlay.h @@ -0,0 +1,16 @@ +#pragma once +#include + +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); +} diff --git a/src/main.cpp b/src/main.cpp index 5c38567..aaf215e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ #include "graphics/effects/Starfield.h" #include "graphics/effects/Starfield3D.h" #include "graphics/ui/Font.h" +#include "graphics/ui/HelpOverlay.h" #include "gameplay/effects/LineEffect.h" #include "states/State.h" #include "states/LoadingState.h" @@ -487,10 +488,10 @@ static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musi // Instructions 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 + 190, "N = PLAY LETS_GO", 1.0f, {200, 200, 220, 255}); - font.draw(renderer, popupX + 20, popupY + 210, "ESC = CLOSE", 1.0f, {200, 200, 220, 255}); + font.draw(renderer, popupX + 20, popupY + 190, "ESC = CLOSE", 1.0f, {200, 200, 220, 255}); } + // ----------------------------------------------------------------------------- // Starfield effect for background // ----------------------------------------------------------------------------- @@ -504,12 +505,14 @@ static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musi // ----------------------------------------------------------------------------- static double logoAnimCounter = 0.0; static bool showSettingsPopup = false; +static bool showHelpOverlay = false; static bool showExitConfirmPopup = false; static int exitPopupSelectedButton = 1; // 0 = YES, 1 = NO static bool musicEnabled = true; static int hoveredButton = -1; // -1 = none, 0 = play, 1 = level, 2 = settings static bool isNewHighScore = false; static std::string playerName = ""; +static bool helpOverlayPausedGame = false; // ----------------------------------------------------------------------------- // Tetris Block Fireworks for intro animation (block particles) @@ -904,6 +907,7 @@ int main(int, char **) ctx.startLevelSelection = &startLevelSelection; ctx.hoveredButton = &hoveredButton; ctx.showSettingsPopup = &showSettingsPopup; + ctx.showHelpOverlay = &showHelpOverlay; ctx.showExitConfirmPopup = &showExitConfirmPopup; ctx.exitPopupSelectedButton = &exitPopupSelectedButton; ctx.gameplayCountdownActive = &gameplayCountdownActive; @@ -1027,12 +1031,22 @@ int main(int, char **) running = false; else { // Route event to state manager handlers for per-state logic - 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(); + const bool isUserInputEvent = + e.type == SDL_EVENT_KEY_DOWN || + e.type == SDL_EVENT_KEY_UP || + e.type == SDL_EVENT_TEXT_INPUT || + e.type == SDL_EVENT_MOUSE_BUTTON_DOWN || + 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) if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) { @@ -1048,11 +1062,23 @@ int main(int, char **) SoundEffectManager::instance().setEnabled(!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 - if (!allVoiceSounds.empty()) { - SoundEffectManager::instance().playRandomSound(allVoiceSounds, 1.0f); + showHelpOverlay = !showHelpOverlay; + if (state == AppState::Playing) { + 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))) @@ -1064,13 +1090,13 @@ int main(int, char **) } // 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) { 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 (e.key.scancode == SDL_SCANCODE_BACKSPACE && !playerName.empty()) { playerName.pop_back(); @@ -1096,7 +1122,7 @@ int main(int, char **) } // 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; 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; 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); } + 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_SetRenderScale(renderer, 1.f, 1.f); } diff --git a/src/states/MenuState.cpp b/src/states/MenuState.cpp index 7344229..40572b7 100644 --- a/src/states/MenuState.cpp +++ b/src/states/MenuState.cpp @@ -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 + 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 + 190, "N = PLAY LETS_GO", 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}); + ctx.font->draw(renderer, popupX + 20, popupY + 190, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255}); } // Trace exit { diff --git a/src/states/PlayingState.cpp b/src/states/PlayingState.cpp index 35e7a0d..01acecd 100644 --- a/src/states/PlayingState.cpp +++ b/src/states/PlayingState.cpp @@ -104,8 +104,7 @@ void PlayingState::handleEvent(const SDL_Event& e) { // Tetris controls (only when not paused) if (!ctx.game->isPaused()) { // Rotation (still event-based for precise timing) - if (e.key.scancode == SDL_SCANCODE_UP || e.key.scancode == SDL_SCANCODE_W || - e.key.scancode == SDL_SCANCODE_Z) { + if (e.key.scancode == SDL_SCANCODE_UP) { ctx.game->rotate(1); // Clockwise rotation return; } diff --git a/src/states/State.h b/src/states/State.h index 87601a8..68bee3c 100644 --- a/src/states/State.h +++ b/src/states/State.h @@ -49,6 +49,7 @@ struct StateContext { int* hoveredButton = nullptr; // Menu popups (exposed from main) bool* showSettingsPopup = nullptr; + bool* showHelpOverlay = nullptr; bool* showExitConfirmPopup = nullptr; // If true, show "Exit game?" confirmation while playing int* exitPopupSelectedButton = nullptr; // 0 = YES, 1 = NO (default) bool* gameplayCountdownActive = nullptr; // True if start-of-game countdown is running diff --git a/src/ui/MenuWrappers.cpp b/src/ui/MenuWrappers.cpp index 62a54d7..cb23a98 100644 --- a/src/ui/MenuWrappers.cpp +++ b/src/ui/MenuWrappers.cpp @@ -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 + 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 + 190, "N = PLAY LETS_GO", 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}); + font.draw(renderer, popupX + 20, popupY + 190, "ESC = CLOSE", 1.0f, SDL_Color{200,200,220,255}); }