fix
This commit is contained in:
@ -85,9 +85,15 @@ void MenuState::renderMainButtonTop(SDL_Renderer* renderer, float logicalScale,
|
||||
if (i == 3) extra = -44.0f;
|
||||
cxCenter = btnX + offset + extra;
|
||||
}
|
||||
// Apply group alpha and transient flash to button colors
|
||||
double aMul = std::clamp(buttonGroupAlpha + buttonFlash * buttonFlashAmount, 0.0, 1.0);
|
||||
SDL_Color bgCol = buttons[i].bg;
|
||||
SDL_Color bdCol = buttons[i].border;
|
||||
bgCol.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(bgCol.a)));
|
||||
bdCol.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(bdCol.a)));
|
||||
UIRenderer::drawButton(renderer, ctx.pixelFont, cxCenter, cyCenter, btnW, btnH,
|
||||
buttons[i].label, false, selectedButton == i,
|
||||
buttons[i].bg, buttons[i].border, true, icons[i]);
|
||||
bgCol, bdCol, true, icons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,36 +137,49 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
}
|
||||
};
|
||||
|
||||
if (isExitPromptVisible()) {
|
||||
// Inline exit HUD handling (replaces the old modal popup)
|
||||
if (exitPanelVisible && !exitPanelAnimating) {
|
||||
switch (e.key.scancode) {
|
||||
case SDL_SCANCODE_LEFT:
|
||||
case SDL_SCANCODE_UP:
|
||||
setExitSelection(0);
|
||||
// Move selection to YES
|
||||
exitSelectedButton = 0;
|
||||
if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
|
||||
return;
|
||||
case SDL_SCANCODE_RIGHT:
|
||||
case SDL_SCANCODE_DOWN:
|
||||
setExitSelection(1);
|
||||
// Move selection to NO
|
||||
exitSelectedButton = 1;
|
||||
if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
|
||||
return;
|
||||
case SDL_SCANCODE_RETURN:
|
||||
case SDL_SCANCODE_KP_ENTER:
|
||||
case SDL_SCANCODE_SPACE:
|
||||
if (getExitSelection() == 0) {
|
||||
setExitPrompt(false);
|
||||
if (exitSelectedButton == 0) {
|
||||
// Confirm quit
|
||||
if (ctx.requestQuit) {
|
||||
ctx.requestQuit();
|
||||
} else {
|
||||
SDL_Event quit{};
|
||||
quit.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&quit);
|
||||
SDL_Event quit{}; quit.type = SDL_EVENT_QUIT; SDL_PushEvent(&quit);
|
||||
}
|
||||
} else {
|
||||
setExitPrompt(false);
|
||||
// Close HUD
|
||||
exitPanelAnimating = true; exitDirection = -1;
|
||||
if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false;
|
||||
}
|
||||
return;
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
setExitPrompt(false);
|
||||
setExitSelection(1);
|
||||
// Close HUD
|
||||
exitPanelAnimating = true; exitDirection = -1;
|
||||
if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false;
|
||||
return;
|
||||
case SDL_SCANCODE_PAGEDOWN:
|
||||
case SDL_SCANCODE_DOWN: {
|
||||
// scroll content down
|
||||
exitScroll += 40.0; return;
|
||||
}
|
||||
case SDL_SCANCODE_PAGEUP:
|
||||
case SDL_SCANCODE_UP: {
|
||||
exitScroll -= 40.0; if (exitScroll < 0.0) exitScroll = 0.0; return;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@ -168,15 +187,17 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
|
||||
// If the inline options HUD is visible and not animating, capture navigation
|
||||
if (optionsVisible && !optionsAnimating) {
|
||||
// Options now has more rows; use OPTIONS_ROW_COUNT
|
||||
constexpr int OPTIONS_ROW_COUNT = 8;
|
||||
switch (e.key.scancode) {
|
||||
case SDL_SCANCODE_UP:
|
||||
{
|
||||
optionsSelectedRow = (optionsSelectedRow + 5 - 1) % 5;
|
||||
optionsSelectedRow = (optionsSelectedRow + OPTIONS_ROW_COUNT - 1) % OPTIONS_ROW_COUNT;
|
||||
return;
|
||||
}
|
||||
case SDL_SCANCODE_DOWN:
|
||||
{
|
||||
optionsSelectedRow = (optionsSelectedRow + 1) % 5;
|
||||
optionsSelectedRow = (optionsSelectedRow + 1) % OPTIONS_ROW_COUNT;
|
||||
return;
|
||||
}
|
||||
case SDL_SCANCODE_LEFT:
|
||||
@ -220,6 +241,25 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
return;
|
||||
}
|
||||
case 4: {
|
||||
// PULSE ENABLE
|
||||
buttonPulseEnabled = !buttonPulseEnabled;
|
||||
return;
|
||||
}
|
||||
case 5: {
|
||||
// PULSE SPEED (Enter toggles to default)
|
||||
if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER || e.key.scancode == SDL_SCANCODE_SPACE) {
|
||||
buttonPulseSpeed = 1.0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 6: {
|
||||
// PULSE MIN ALPHA (Enter resets)
|
||||
if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER || e.key.scancode == SDL_SCANCODE_SPACE) {
|
||||
buttonPulseMinAlpha = 0.6;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 7: {
|
||||
// RETURN TO MENU -> hide panel
|
||||
optionsAnimating = true;
|
||||
optionsDirection = -1;
|
||||
@ -234,7 +274,8 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
|
||||
// If inline level HUD visible and not animating, capture navigation
|
||||
if (levelPanelVisible && !levelPanelAnimating) {
|
||||
int c = levelSelected < 0 ? 0 : levelSelected;
|
||||
// Start navigation from tentative hover if present, otherwise from committed selection
|
||||
int c = (levelHovered >= 0) ? levelHovered : (levelSelected < 0 ? 0 : levelSelected);
|
||||
switch (e.key.scancode) {
|
||||
case SDL_SCANCODE_LEFT: if (c % 4 > 0) c--; break;
|
||||
case SDL_SCANCODE_RIGHT: if (c % 4 < 3) c++; break;
|
||||
@ -243,18 +284,22 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
case SDL_SCANCODE_RETURN:
|
||||
case SDL_SCANCODE_KP_ENTER:
|
||||
case SDL_SCANCODE_SPACE:
|
||||
// Confirm tentative selection
|
||||
levelSelected = c;
|
||||
if (ctx.startLevelSelection) *ctx.startLevelSelection = levelSelected;
|
||||
// close HUD
|
||||
levelPanelAnimating = true; levelDirection = -1;
|
||||
// clear hover candidate
|
||||
levelHovered = -1;
|
||||
return;
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
levelPanelAnimating = true; levelDirection = -1;
|
||||
levelHovered = -1;
|
||||
return;
|
||||
default: break;
|
||||
}
|
||||
levelSelected = c;
|
||||
if (ctx.startLevelSelection) *ctx.startLevelSelection = levelSelected;
|
||||
// Move tentative cursor (don't commit to startLevelSelection yet)
|
||||
levelHovered = c;
|
||||
// Consume the event so main menu navigation does not also run
|
||||
return;
|
||||
}
|
||||
@ -265,6 +310,8 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
{
|
||||
const int total = 4;
|
||||
selectedButton = (selectedButton + total - 1) % total;
|
||||
// brief bright flash on navigation
|
||||
buttonFlash = 1.0;
|
||||
break;
|
||||
}
|
||||
case SDL_SCANCODE_RIGHT:
|
||||
@ -272,6 +319,8 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
{
|
||||
const int total = 4;
|
||||
selectedButton = (selectedButton + 1) % total;
|
||||
// brief bright flash on navigation
|
||||
buttonFlash = 1.0;
|
||||
break;
|
||||
}
|
||||
case SDL_SCANCODE_RETURN:
|
||||
@ -289,6 +338,8 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
if (!levelPanelVisible && !levelPanelAnimating) {
|
||||
levelPanelAnimating = true;
|
||||
levelDirection = 1; // show
|
||||
// initialize tentative cursor to current selected level
|
||||
levelHovered = levelSelected < 0 ? 0 : levelSelected;
|
||||
} else if (levelPanelVisible && !levelPanelAnimating) {
|
||||
levelPanelAnimating = true;
|
||||
levelDirection = -1; // hide
|
||||
@ -305,8 +356,14 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
setExitPrompt(true);
|
||||
setExitSelection(1);
|
||||
// Show the inline exit HUD
|
||||
if (!exitPanelVisible && !exitPanelAnimating) {
|
||||
exitPanelAnimating = true;
|
||||
exitDirection = 1;
|
||||
exitSelectedButton = 1;
|
||||
if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = true;
|
||||
if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -317,8 +374,14 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
optionsDirection = -1;
|
||||
return;
|
||||
}
|
||||
setExitPrompt(true);
|
||||
setExitSelection(1);
|
||||
// Show inline exit HUD on ESC
|
||||
if (!exitPanelVisible && !exitPanelAnimating) {
|
||||
exitPanelAnimating = true;
|
||||
exitDirection = 1;
|
||||
exitSelectedButton = 1;
|
||||
if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = true;
|
||||
if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -359,6 +422,21 @@ void MenuState::update(double frameMs) {
|
||||
}
|
||||
}
|
||||
|
||||
// Advance exit panel animation if active
|
||||
if (exitPanelAnimating) {
|
||||
double delta = (frameMs / exitTransitionDurationMs) * static_cast<double>(exitDirection);
|
||||
exitTransition += delta;
|
||||
if (exitTransition >= 1.0) {
|
||||
exitTransition = 1.0;
|
||||
exitPanelVisible = true;
|
||||
exitPanelAnimating = false;
|
||||
} else if (exitTransition <= 0.0) {
|
||||
exitTransition = 0.0;
|
||||
exitPanelVisible = false;
|
||||
exitPanelAnimating = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Animate level selection highlight position toward the selected cell center
|
||||
if (levelTransition > 0.0 && (lastLogicalScale > 0.0f)) {
|
||||
// Recompute same grid geometry used in render to find target center
|
||||
@ -381,7 +459,7 @@ void MenuState::update(double frameMs) {
|
||||
float cellW = (area.w - (cols - 1) * gapX) / cols;
|
||||
float cellH = (area.h - (rows - 1) * gapY) / rows;
|
||||
|
||||
int targetIdx = std::clamp(levelSelected, 0, 19);
|
||||
int targetIdx = std::clamp((levelHovered >= 0 ? levelHovered : levelSelected), 0, 19);
|
||||
int tr = targetIdx / cols, tc = targetIdx % cols;
|
||||
double targetX = area.x + tc * (cellW + gapX) + cellW * 0.5f;
|
||||
double targetY = area.y + tr * (cellH + gapY) + cellH * 0.5f;
|
||||
@ -399,6 +477,47 @@ void MenuState::update(double frameMs) {
|
||||
levelHighlightY += (targetY - levelHighlightY) * alpha;
|
||||
}
|
||||
}
|
||||
|
||||
// Update button group pulsing animation
|
||||
if (buttonPulseEnabled) {
|
||||
buttonPulseTime += frameMs;
|
||||
double t = (buttonPulseTime * 0.001) * buttonPulseSpeed; // seconds * speed
|
||||
double s = 0.0;
|
||||
switch (buttonPulseEasing) {
|
||||
case 0: // sin
|
||||
s = (std::sin(t * 2.0 * 3.14159265358979323846) * 0.5) + 0.5;
|
||||
break;
|
||||
case 1: // triangle
|
||||
{
|
||||
double ft = t - std::floor(t);
|
||||
s = (ft < 0.5) ? (ft * 2.0) : (2.0 - ft * 2.0);
|
||||
break;
|
||||
}
|
||||
case 2: // exponential ease-in-out (normalized)
|
||||
{
|
||||
double ft = t - std::floor(t);
|
||||
if (ft < 0.5) {
|
||||
s = 0.5 * std::pow(2.0, 20.0 * ft - 10.0);
|
||||
} else {
|
||||
s = 1.0 - 0.5 * std::pow(2.0, -20.0 * ft + 10.0);
|
||||
}
|
||||
// Clamp to 0..1 in case of numeric issues
|
||||
if (s < 0.0) s = 0.0; if (s > 1.0) s = 1.0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
s = (std::sin(t * 2.0 * 3.14159265358979323846) * 0.5) + 0.5;
|
||||
}
|
||||
buttonGroupAlpha = buttonPulseMinAlpha + s * (buttonPulseMaxAlpha - buttonPulseMinAlpha);
|
||||
} else {
|
||||
buttonGroupAlpha = 1.0;
|
||||
}
|
||||
|
||||
// Update flash decay
|
||||
if (buttonFlash > 0.0) {
|
||||
buttonFlash -= frameMs * buttonFlashDecay;
|
||||
if (buttonFlash < 0.0) buttonFlash = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
|
||||
@ -439,8 +558,8 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
// Slide-space amount for the options HUD (how much highscores move)
|
||||
const float moveAmount = 420.0f; // increased so lower score rows slide further up
|
||||
|
||||
// Compute eased transition and delta to shift highscores when either options or level HUD is shown.
|
||||
float combinedTransition = static_cast<float>(std::max(optionsTransition, levelTransition));
|
||||
// Compute eased transition and delta to shift highscores when either options, level, or exit HUD is shown.
|
||||
float combinedTransition = static_cast<float>(std::max(std::max(optionsTransition, levelTransition), exitTransition));
|
||||
float eased = combinedTransition * combinedTransition * (3.0f - 2.0f * combinedTransition); // cubic smoothstep
|
||||
float panelDelta = eased * moveAmount;
|
||||
|
||||
@ -617,15 +736,59 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.showExitConfirmPopup && *ctx.showExitConfirmPopup) {
|
||||
GameRenderer::renderExitPopup(
|
||||
renderer,
|
||||
ctx.pixelFont,
|
||||
winW,
|
||||
winH,
|
||||
logicalScale,
|
||||
ctx.exitPopupSelectedButton ? *ctx.exitPopupSelectedButton : 1
|
||||
);
|
||||
// Inline exit HUD (no opaque background) - slides into the highscores area
|
||||
if (exitTransition > 0.0) {
|
||||
float easedE = static_cast<float>(exitTransition);
|
||||
easedE = easedE * easedE * (3.0f - 2.0f * easedE);
|
||||
const float panelW = 520.0f;
|
||||
const float panelH = 360.0f;
|
||||
float panelBaseX = (LOGICAL_W - panelW) * 0.5f + contentOffsetX;
|
||||
float panelBaseY = (LOGICAL_H - panelH) * 0.5f + contentOffsetY - (LOGICAL_H * 0.10f);
|
||||
float slideAmount = LOGICAL_H * 0.42f;
|
||||
float panelY = panelBaseY + (1.0f - easedE) * slideAmount;
|
||||
|
||||
FontAtlas* titleFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
||||
if (titleFont) titleFont->draw(renderer, panelBaseX + 12.0f, panelY + 6.0f, "EXIT GAME?", 1.6f, SDL_Color{255,200,80,220});
|
||||
|
||||
SDL_FRect area{ panelBaseX + 12.0f, panelY + 56.0f, panelW - 24.0f, panelH - 120.0f };
|
||||
// Sample long message (scrollable)
|
||||
// Paragraph-style lines for a nicer exit confirmation message
|
||||
std::vector<std::string> lines = {
|
||||
"Quit now to return to your desktop. Your current session will end.",
|
||||
"Press YES to quit immediately, or NO to return to the menu and continue playing.",
|
||||
"Adjust audio, controls and other settings anytime from the Options menu.",
|
||||
"Thanks for playing SPACETRIS — we hope to see you again!"
|
||||
};
|
||||
|
||||
// Draw scrollable text (no background box; increased line spacing)
|
||||
FontAtlas* f = ctx.font ? ctx.font : ctx.pixelFont;
|
||||
float y = area.y - (float)exitScroll;
|
||||
const float lineSpacing = 34.0f; // increased spacing for readability
|
||||
if (f) {
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
f->draw(renderer, area.x + 6.0f, y + i * lineSpacing, lines[i], 1.0f, SDL_Color{200,220,240,220});
|
||||
}
|
||||
}
|
||||
|
||||
// Draw buttons at bottom of panel
|
||||
float btnW2 = 160.0f, btnH2 = 48.0f;
|
||||
float bx = panelBaseX + (panelW - (btnW2 * 2.0f + 12.0f)) * 0.5f;
|
||||
float by = panelY + panelH - 56.0f;
|
||||
// YES button
|
||||
SDL_Color yesBg{220,80,60, 200}; SDL_Color yesBorder{160,40,40,200};
|
||||
SDL_Color noBg{60,140,200,200}; SDL_Color noBorder{30,90,160,200};
|
||||
// Apply pulse alpha to buttons
|
||||
double aMul = std::clamp(buttonGroupAlpha + buttonFlash * buttonFlashAmount, 0.0, 1.0);
|
||||
yesBg.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(yesBg.a)));
|
||||
yesBorder.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(yesBorder.a)));
|
||||
noBg.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(noBg.a)));
|
||||
noBorder.a = static_cast<Uint8>(std::round(aMul * static_cast<double>(noBorder.a)));
|
||||
|
||||
UIRenderer::drawButton(renderer, ctx.pixelFont, bx + btnW2*0.5f, by, btnW2, btnH2, "YES", false, exitSelectedButton == 0, yesBg, yesBorder, true, nullptr);
|
||||
UIRenderer::drawButton(renderer, ctx.pixelFont, bx + btnW2*1.5f + 12.0f, by, btnW2, btnH2, "NO", false, exitSelectedButton == 1, noBg, noBorder, true, nullptr);
|
||||
|
||||
// Ensure ctx mirrors selection for any other code
|
||||
if (ctx.exitPopupSelectedButton) *ctx.exitPopupSelectedButton = exitSelectedButton;
|
||||
}
|
||||
|
||||
// Popups (settings only - level popup is now a separate state)
|
||||
|
||||
@ -53,4 +53,25 @@ private:
|
||||
double levelHighlightGlowAlpha = 0.70; // 0..1 base glow alpha
|
||||
int levelHighlightThickness = 3; // inner outline thickness (px)
|
||||
SDL_Color levelHighlightColor = SDL_Color{80, 200, 255, 200};
|
||||
// Button group pulsing/fade parameters (applies to all four main buttons)
|
||||
double buttonGroupAlpha = 1.0; // current computed alpha (0..1)
|
||||
double buttonPulseTime = 0.0; // accumulator in ms
|
||||
bool buttonPulseEnabled = true; // enable/disable pulsing
|
||||
double buttonPulseSpeed = 1.0; // multiplier for pulse frequency
|
||||
double buttonPulseMinAlpha = 0.60; // minimum alpha during pulse
|
||||
double buttonPulseMaxAlpha = 1.00; // maximum alpha during pulse
|
||||
// Pulse easing mode: 0=sin,1=triangle,2=exponential
|
||||
int buttonPulseEasing = 1;
|
||||
// Short bright flash when navigating buttons
|
||||
double buttonFlash = 0.0; // transient flash amount (0..1)
|
||||
double buttonFlashAmount = 0.45; // how much flash adds to group alpha
|
||||
double buttonFlashDecay = 0.0025; // linear decay per ms
|
||||
// Exit confirmation HUD state (inline HUD like Options/Level)
|
||||
bool exitPanelVisible = false;
|
||||
bool exitPanelAnimating = false;
|
||||
double exitTransition = 0.0; // 0..1
|
||||
double exitTransitionDurationMs = 360.0;
|
||||
int exitDirection = 1; // 1 show, -1 hide
|
||||
int exitSelectedButton = 0; // 0 = YES (quit), 1 = NO (cancel)
|
||||
double exitScroll = 0.0; // vertical scroll offset for content
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user