fixed menu
This commit is contained in:
@ -112,7 +112,7 @@ static void renderBackdropBlur(SDL_Renderer* renderer, const SDL_Rect& logicalVP
|
|||||||
|
|
||||||
MenuState::MenuState(StateContext& ctx) : State(ctx) {}
|
MenuState::MenuState(StateContext& ctx) : State(ctx) {}
|
||||||
|
|
||||||
void MenuState::showCoopSetupPanel(bool show) {
|
void MenuState::showCoopSetupPanel(bool show, bool resumeMusic) {
|
||||||
if (show) {
|
if (show) {
|
||||||
if (!coopSetupVisible && !coopSetupAnimating) {
|
if (!coopSetupVisible && !coopSetupAnimating) {
|
||||||
// Avoid overlapping panels
|
// Avoid overlapping panels
|
||||||
@ -152,8 +152,8 @@ void MenuState::showCoopSetupPanel(bool show) {
|
|||||||
coopSetupAnimating = true;
|
coopSetupAnimating = true;
|
||||||
coopSetupDirection = -1;
|
coopSetupDirection = -1;
|
||||||
coopSetupRectsValid = false;
|
coopSetupRectsValid = false;
|
||||||
// Ensure menu music resumes when closing the coop setup panel
|
// Resume menu music only when requested (ESC should pass resumeMusic=false)
|
||||||
if (ctx.musicEnabled && *ctx.musicEnabled) {
|
if (resumeMusic && ctx.musicEnabled && *ctx.musicEnabled) {
|
||||||
Audio::instance().playMenuMusic();
|
Audio::instance().playMenuMusic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,9 +280,70 @@ void MenuState::onExit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MenuState::handleEvent(const SDL_Event& e) {
|
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
|
// 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 (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
|
||||||
if (coopSetupRectsValid) {
|
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<float>(e.button.x);
|
const float mx = static_cast<float>(e.button.x);
|
||||||
const float my = static_cast<float>(e.button.y);
|
const float my = static_cast<float>(e.button.y);
|
||||||
if (mx >= lastLogicalVP.x && my >= lastLogicalVP.y && mx <= (lastLogicalVP.x + lastLogicalVP.w) && my <= (lastLogicalVP.y + lastLogicalVP.h)) {
|
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;
|
return;
|
||||||
case SDL_SCANCODE_ESCAPE:
|
case SDL_SCANCODE_ESCAPE:
|
||||||
// Close HUD
|
showCoopSetupPanel(false, true);
|
||||||
exitPanelAnimating = true; exitDirection = -1;
|
// 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;
|
if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false;
|
||||||
return;
|
return;
|
||||||
case SDL_SCANCODE_PAGEDOWN:
|
case SDL_SCANCODE_PAGEDOWN:
|
||||||
@ -555,17 +629,20 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
if ((coopSetupVisible || coopSetupAnimating) && coopSetupTransition > 0.0) {
|
if ((coopSetupVisible || coopSetupAnimating) && coopSetupTransition > 0.0) {
|
||||||
switch (e.key.scancode) {
|
switch (e.key.scancode) {
|
||||||
case SDL_SCANCODE_LEFT:
|
case SDL_SCANCODE_LEFT:
|
||||||
case SDL_SCANCODE_A:
|
|
||||||
coopSetupSelected = 0;
|
coopSetupSelected = 0;
|
||||||
buttonFlash = 1.0;
|
buttonFlash = 1.0;
|
||||||
return;
|
return;
|
||||||
case SDL_SCANCODE_RIGHT:
|
case SDL_SCANCODE_RIGHT:
|
||||||
case SDL_SCANCODE_D:
|
|
||||||
coopSetupSelected = 1;
|
coopSetupSelected = 1;
|
||||||
buttonFlash = 1.0;
|
buttonFlash = 1.0;
|
||||||
return;
|
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:
|
case SDL_SCANCODE_ESCAPE:
|
||||||
showCoopSetupPanel(false);
|
// Close coop panel without restarting music
|
||||||
|
showCoopSetupPanel(false, false);
|
||||||
return;
|
return;
|
||||||
case SDL_SCANCODE_RETURN:
|
case SDL_SCANCODE_RETURN:
|
||||||
case SDL_SCANCODE_KP_ENTER:
|
case SDL_SCANCODE_KP_ENTER:
|
||||||
@ -575,7 +652,6 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
if (ctx.coopVsAI) {
|
if (ctx.coopVsAI) {
|
||||||
*ctx.coopVsAI = useAI;
|
*ctx.coopVsAI = useAI;
|
||||||
}
|
}
|
||||||
// Start cooperative play
|
|
||||||
if (ctx.game) {
|
if (ctx.game) {
|
||||||
ctx.game->setMode(GameMode::Cooperate);
|
ctx.game->setMode(GameMode::Cooperate);
|
||||||
ctx.game->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
|
ctx.game->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
|
||||||
@ -583,7 +659,7 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
if (ctx.coopGame) {
|
if (ctx.coopGame) {
|
||||||
ctx.coopGame->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
|
ctx.coopGame->reset(ctx.startLevelSelection ? *ctx.startLevelSelection : 0);
|
||||||
}
|
}
|
||||||
showCoopSetupPanel(false);
|
showCoopSetupPanel(false, false);
|
||||||
triggerPlay();
|
triggerPlay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -695,7 +771,7 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_ESCAPE:
|
case SDL_SCANCODE_ESCAPE:
|
||||||
if (coopSetupVisible && !coopSetupAnimating) {
|
if (coopSetupVisible && !coopSetupAnimating) {
|
||||||
showCoopSetupPanel(false);
|
showCoopSetupPanel(false, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If options panel is visible, hide it first.
|
// 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
|
// 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<float>(coopSetupTransition);
|
||||||
|
if (alphaFactor < 0.0f) alphaFactor = 0.0f;
|
||||||
|
if (alphaFactor > 1.0f) alphaFactor = 1.0f;
|
||||||
if (coopInfoTexture && coopInfoTexW > 0 && coopInfoTexH > 0) {
|
if (coopInfoTexture && coopInfoTexW > 0 && coopInfoTexH > 0) {
|
||||||
float totalW = btnW2 * 2.0f + gap;
|
float totalW = btnW2 * 2.0f + gap;
|
||||||
// Increase allowed image width by ~15% (was 0.75 of totalW)
|
// 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;
|
if (imgY < minY) imgY = minY;
|
||||||
SDL_FRect dst{ imgX, imgY, targetW, targetH };
|
SDL_FRect dst{ imgX, imgY, targetW, targetH };
|
||||||
SDL_SetTextureBlendMode(coopInfoTexture, SDL_BLENDMODE_BLEND);
|
SDL_SetTextureBlendMode(coopInfoTexture, SDL_BLENDMODE_BLEND);
|
||||||
// Make the coop info image slightly transparent
|
// Make the coop info image slightly transparent scaled by transition
|
||||||
SDL_SetTextureAlphaMod(coopInfoTexture, 200);
|
SDL_SetTextureAlphaMod(coopInfoTexture, static_cast<Uint8>(std::round(200.0f * alphaFactor)));
|
||||||
SDL_RenderTexture(renderer, coopInfoTexture, nullptr, &dst);
|
SDL_RenderTexture(renderer, coopInfoTexture, nullptr, &dst);
|
||||||
|
|
||||||
// Draw cooperative instructions inside the panel area (overlayed on the panel background)
|
// 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;
|
float bulletScale = 0.78f;
|
||||||
SDL_Color bulletCol{200,220,230,220};
|
SDL_Color bulletCol{200,220,230,220};
|
||||||
|
bulletCol.a = static_cast<Uint8>(std::round(bulletCol.a * alphaFactor));
|
||||||
int sampleLW = 0, sampleLH = 0;
|
int sampleLW = 0, sampleLH = 0;
|
||||||
f->measure(bullets[0], bulletScale, sampleLW, sampleLH);
|
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);
|
int hW=0, hH=0; f->measure(header, headerScale, hW, hH);
|
||||||
float hx = panelBaseX + (panelW - static_cast<float>(hW)) * 0.5f + 40.0f; // nudge header right by 40px
|
float hx = panelBaseX + (panelW - static_cast<float>(hW)) * 0.5f + 40.0f; // nudge header right by 40px
|
||||||
float headerY = textY - static_cast<float>(sampleLH);
|
float headerY = textY - static_cast<float>(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<Uint8>(std::round(headerCol.a * alphaFactor));
|
||||||
|
f->draw(renderer, hx, headerY, header, headerScale, headerCol);
|
||||||
// Start body text slightly below header
|
// Start body text slightly below header
|
||||||
textY = headerY + static_cast<float>(hH) + 8.0f;
|
textY = headerY + static_cast<float>(hH) + 8.0f;
|
||||||
|
|
||||||
@ -1379,18 +1461,28 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
// GOAL section (aligned with shifted bullets)
|
// GOAL section (aligned with shifted bullets)
|
||||||
textY += 6.0f;
|
textY += 6.0f;
|
||||||
std::string goalTitle = "GOAL:";
|
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<Uint8>(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);
|
int gW=0, gH=0; f->measure(goalTitle, 0.88f, gW, gH);
|
||||||
float goalX = bulletX + static_cast<float>(gW) + 10.0f;
|
float goalX = bulletX + static_cast<float>(gW) + 10.0f;
|
||||||
std::string goalText = "Clear lines together and achieve the highest TEAM SCORE";
|
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<Uint8>(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<Uint8>(std::round(bgA.a * buttonFade));
|
||||||
|
SDL_Color borderA = border; borderA.a = static_cast<Uint8>(std::round(borderA.a * buttonFade));
|
||||||
UIRenderer::drawButton(renderer, ctx.pixelFont, coopSetupBtnRects[0].x + btnW2 * 0.5f, coopSetupBtnRects[0].y + btnH2 * 0.5f,
|
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,
|
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
|
// NOTE: slide-up COOP panel intentionally removed. Only the inline
|
||||||
// highscores-area choice buttons are shown when coop setup is active.
|
// highscores-area choice buttons are shown when coop setup is active.
|
||||||
|
|||||||
@ -21,7 +21,8 @@ public:
|
|||||||
void showAboutPanel(bool show);
|
void showAboutPanel(bool show);
|
||||||
|
|
||||||
// Show or hide the inline COOPERATE setup panel (2P vs AI).
|
// 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:
|
private:
|
||||||
int selectedButton = 0; // 0=PLAY,1=COOPERATE,2=CHALLENGE,3=LEVEL,4=OPTIONS,5=HELP,6=ABOUT,7=EXIT
|
int selectedButton = 0; // 0=PLAY,1=COOPERATE,2=CHALLENGE,3=LEVEL,4=OPTIONS,5=HELP,6=ABOUT,7=EXIT
|
||||||
|
|||||||
Reference in New Issue
Block a user