From b450e2af21c45a21d0bea60d0b42c557ac3377b7 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Tue, 23 Dec 2025 14:49:55 +0100 Subject: [PATCH] fixed menu --- src/states/MenuState.cpp | 128 +++++++++++++++++++++++++++++++++------ src/states/MenuState.h | 3 +- 2 files changed, 112 insertions(+), 19 deletions(-) diff --git a/src/states/MenuState.cpp b/src/states/MenuState.cpp index 93e2164..bdcd0fa 100644 --- a/src/states/MenuState.cpp +++ b/src/states/MenuState.cpp @@ -112,7 +112,7 @@ static void renderBackdropBlur(SDL_Renderer* renderer, const SDL_Rect& logicalVP MenuState::MenuState(StateContext& ctx) : State(ctx) {} -void MenuState::showCoopSetupPanel(bool show) { +void MenuState::showCoopSetupPanel(bool show, bool resumeMusic) { if (show) { if (!coopSetupVisible && !coopSetupAnimating) { // Avoid overlapping panels @@ -152,8 +152,8 @@ void MenuState::showCoopSetupPanel(bool show) { coopSetupAnimating = true; coopSetupDirection = -1; coopSetupRectsValid = false; - // Ensure menu music resumes when closing the coop setup panel - if (ctx.musicEnabled && *ctx.musicEnabled) { + // Resume menu music only when requested (ESC should pass resumeMusic=false) + if (resumeMusic && ctx.musicEnabled && *ctx.musicEnabled) { Audio::instance().playMenuMusic(); } } @@ -280,9 +280,70 @@ void MenuState::onExit() { } void MenuState::handleEvent(const SDL_Event& e) { + // Coop setup panel navigation (modal within the menu) + // Handle this FIRST and consume key events so the main menu navigation doesn't interfere. + // Note: Do not require !repeat here; some keyboards/OS configs may emit Enter with repeat. + if ((coopSetupVisible || coopSetupAnimating) && coopSetupTransition > 0.0 && e.type == SDL_EVENT_KEY_DOWN) { + switch (e.key.scancode) { + case SDL_SCANCODE_LEFT: + case SDL_SCANCODE_A: + coopSetupSelected = 0; + buttonFlash = 1.0; + return; + case SDL_SCANCODE_RIGHT: + case SDL_SCANCODE_D: + coopSetupSelected = 1; + buttonFlash = 1.0; + return; + // Do NOT allow up/down to change anything + case SDL_SCANCODE_UP: + case SDL_SCANCODE_DOWN: + return; + case SDL_SCANCODE_ESCAPE: + showCoopSetupPanel(false, false); + return; + case SDL_SCANCODE_RETURN: + case SDL_SCANCODE_KP_ENTER: + case SDL_SCANCODE_SPACE: + { + const bool useAI = (coopSetupSelected == 1); + if (ctx.coopVsAI) { + *ctx.coopVsAI = useAI; + } + if (ctx.game) { + ctx.game->setMode(GameMode::Cooperate); + ctx.game->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0); + } + if (ctx.coopGame) { + ctx.coopGame->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0); + } + + // Close the panel without restarting menu music; gameplay will take over. + showCoopSetupPanel(false, false); + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: coop start via key, selected=%d, startPlayTransition_present=%d, stateManager=%p", coopSetupSelected, ctx.startPlayTransition ? 1 : 0, (void*)ctx.stateManager); + + if (ctx.startPlayTransition) { + ctx.startPlayTransition(); + } else if (ctx.stateManager) { + ctx.stateManager->setState(AppState::Playing); + } + return; + } + default: + // Allow all other keys to be pressed, but don't let them affect the main menu while coop is open. + return; + } + } + // Mouse input for COOP setup panel or inline coop buttons if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) { if (coopSetupRectsValid) { + // While the coop submenu is active (animating or visible) we disallow + // mouse interaction — only keyboard LEFT/RIGHT/ESC is permitted. + if (coopSetupAnimating || coopSetupVisible) { + return; + } const float mx = static_cast(e.button.x); const float my = static_cast(e.button.y); if (mx >= lastLogicalVP.x && my >= lastLogicalVP.y && mx <= (lastLogicalVP.x + lastLogicalVP.w) && my <= (lastLogicalVP.y + lastLogicalVP.h)) { @@ -384,8 +445,21 @@ void MenuState::handleEvent(const SDL_Event& e) { } return; case SDL_SCANCODE_ESCAPE: - // Close HUD - exitPanelAnimating = true; exitDirection = -1; + showCoopSetupPanel(false, true); + // Cannot print std::function as a pointer; print presence (1/0) instead + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: coop ENTER pressed, selected=%d, startPlayTransition_present=%d, stateManager=%p", coopSetupSelected, ctx.startPlayTransition ? 1 : 0, (void*)ctx.stateManager); + if (ctx.startPlayTransition) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: calling startPlayTransition"); + ctx.startPlayTransition(); + } else { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: startPlayTransition is null"); + } + if (ctx.stateManager) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: setting AppState::Playing on stateManager"); + ctx.stateManager->setState(AppState::Playing); + } else { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState: stateManager is null"); + } if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false; return; case SDL_SCANCODE_PAGEDOWN: @@ -555,17 +629,20 @@ void MenuState::handleEvent(const SDL_Event& e) { if ((coopSetupVisible || coopSetupAnimating) && coopSetupTransition > 0.0) { switch (e.key.scancode) { case SDL_SCANCODE_LEFT: - case SDL_SCANCODE_A: coopSetupSelected = 0; buttonFlash = 1.0; return; case SDL_SCANCODE_RIGHT: - case SDL_SCANCODE_D: coopSetupSelected = 1; buttonFlash = 1.0; return; + // Explicitly consume Up/Down so main menu navigation doesn't trigger + case SDL_SCANCODE_UP: + case SDL_SCANCODE_DOWN: + return; case SDL_SCANCODE_ESCAPE: - showCoopSetupPanel(false); + // Close coop panel without restarting music + showCoopSetupPanel(false, false); return; case SDL_SCANCODE_RETURN: case SDL_SCANCODE_KP_ENTER: @@ -575,7 +652,6 @@ void MenuState::handleEvent(const SDL_Event& e) { if (ctx.coopVsAI) { *ctx.coopVsAI = useAI; } - // Start cooperative play if (ctx.game) { ctx.game->setMode(GameMode::Cooperate); ctx.game->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0); @@ -583,7 +659,7 @@ void MenuState::handleEvent(const SDL_Event& e) { if (ctx.coopGame) { ctx.coopGame->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0); } - showCoopSetupPanel(false); + showCoopSetupPanel(false, false); triggerPlay(); return; } @@ -695,7 +771,7 @@ void MenuState::handleEvent(const SDL_Event& e) { break; case SDL_SCANCODE_ESCAPE: if (coopSetupVisible && !coopSetupAnimating) { - showCoopSetupPanel(false); + showCoopSetupPanel(false, false); return; } // If options panel is visible, hide it first. @@ -1317,6 +1393,10 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi } // If the image loaded, render it centered above the two choice buttons + // Compute fade alpha from the coop transition so it can be used for image, text and buttons + float alphaFactor = static_cast(coopSetupTransition); + if (alphaFactor < 0.0f) alphaFactor = 0.0f; + if (alphaFactor > 1.0f) alphaFactor = 1.0f; if (coopInfoTexture && coopInfoTexW > 0 && coopInfoTexH > 0) { float totalW = btnW2 * 2.0f + gap; // Increase allowed image width by ~15% (was 0.75 of totalW) @@ -1331,8 +1411,8 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi if (imgY < minY) imgY = minY; SDL_FRect dst{ imgX, imgY, targetW, targetH }; SDL_SetTextureBlendMode(coopInfoTexture, SDL_BLENDMODE_BLEND); - // Make the coop info image slightly transparent - SDL_SetTextureAlphaMod(coopInfoTexture, 200); + // Make the coop info image slightly transparent scaled by transition + SDL_SetTextureAlphaMod(coopInfoTexture, static_cast(std::round(200.0f * alphaFactor))); SDL_RenderTexture(renderer, coopInfoTexture, nullptr, &dst); // Draw cooperative instructions inside the panel area (overlayed on the panel background) @@ -1353,6 +1433,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi }; float bulletScale = 0.78f; SDL_Color bulletCol{200,220,230,220}; + bulletCol.a = static_cast(std::round(bulletCol.a * alphaFactor)); int sampleLW = 0, sampleLH = 0; f->measure(bullets[0], bulletScale, sampleLW, sampleLH); @@ -1362,7 +1443,8 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi int hW=0, hH=0; f->measure(header, headerScale, hW, hH); float hx = panelBaseX + (panelW - static_cast(hW)) * 0.5f + 40.0f; // nudge header right by 40px float headerY = textY - static_cast(sampleLH); - f->draw(renderer, hx, headerY, header, headerScale, SDL_Color{220,240,255,230}); + SDL_Color headerCol = SDL_Color{220,240,255,230}; headerCol.a = static_cast(std::round(headerCol.a * alphaFactor)); + f->draw(renderer, hx, headerY, header, headerScale, headerCol); // Start body text slightly below header textY = headerY + static_cast(hH) + 8.0f; @@ -1379,18 +1461,28 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi // GOAL section (aligned with shifted bullets) textY += 6.0f; std::string goalTitle = "GOAL:"; - f->draw(renderer, bulletX, textY, goalTitle, 0.88f, SDL_Color{255,215,80,230}); + SDL_Color goalTitleCol = SDL_Color{255,215,80,230}; goalTitleCol.a = static_cast(std::round(goalTitleCol.a * alphaFactor)); + f->draw(renderer, bulletX, textY, goalTitle, 0.88f, goalTitleCol); int gW=0, gH=0; f->measure(goalTitle, 0.88f, gW, gH); float goalX = bulletX + static_cast(gW) + 10.0f; std::string goalText = "Clear lines together and achieve the highest TEAM SCORE"; - f->draw(renderer, goalX, textY, goalText, 0.86f, SDL_Color{220,240,255,220}); + SDL_Color goalTextCol = SDL_Color{220,240,255,220}; goalTextCol.a = static_cast(std::round(goalTextCol.a * alphaFactor)); + f->draw(renderer, goalX, textY, goalText, 0.86f, goalTextCol); } } + // Delay + eased fade specifically for the two coop buttons so they appear after the image/text. + const float btnDelay = 0.25f; // fraction of transition to wait before buttons start fading + float rawBtn = (alphaFactor - btnDelay) / (1.0f - btnDelay); + rawBtn = std::clamp(rawBtn, 0.0f, 1.0f); + // ease-in (squared) for a slower, smoother fade + float buttonFade = rawBtn * rawBtn; + SDL_Color bgA = bg; bgA.a = static_cast(std::round(bgA.a * buttonFade)); + SDL_Color borderA = border; borderA.a = static_cast(std::round(borderA.a * buttonFade)); UIRenderer::drawButton(renderer, ctx.pixelFont, coopSetupBtnRects[0].x + btnW2 * 0.5f, coopSetupBtnRects[0].y + btnH2 * 0.5f, - btnW2, btnH2, "2 PLAYERS", false, coopSetupSelected == 0, bg, border, false, nullptr); + btnW2, btnH2, "2 PLAYERS", false, coopSetupSelected == 0, bgA, borderA, false, nullptr); UIRenderer::drawButton(renderer, ctx.pixelFont, coopSetupBtnRects[1].x + btnW2 * 0.5f, coopSetupBtnRects[1].y + btnH2 * 0.5f, - btnW2, btnH2, "COMPUTER (AI)", false, coopSetupSelected == 1, bg, border, false, nullptr); + btnW2, btnH2, "COMPUTER (AI)", false, coopSetupSelected == 1, bgA, borderA, false, nullptr); } // NOTE: slide-up COOP panel intentionally removed. Only the inline // highscores-area choice buttons are shown when coop setup is active. diff --git a/src/states/MenuState.h b/src/states/MenuState.h index 425fcac..20d41ae 100644 --- a/src/states/MenuState.h +++ b/src/states/MenuState.h @@ -21,7 +21,8 @@ public: void showAboutPanel(bool show); // Show or hide the inline COOPERATE setup panel (2P vs AI). - void showCoopSetupPanel(bool show); + // If `resumeMusic` is false when hiding, the menu music will not be restarted. + void showCoopSetupPanel(bool show, bool resumeMusic = true); private: int selectedButton = 0; // 0=PLAY,1=COOPERATE,2=CHALLENGE,3=LEVEL,4=OPTIONS,5=HELP,6=ABOUT,7=EXIT