new background image
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 1.2 MiB |
BIN
assets/images/main_screen_old.png
Normal file
BIN
assets/images/main_screen_old.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@ -107,8 +107,22 @@ void SpaceWarp::spawnComet() {
|
|||||||
float normalizedAspect = std::max(aspect, MIN_ASPECT);
|
float normalizedAspect = std::max(aspect, MIN_ASPECT);
|
||||||
float xRange = settings.baseSpawnRange * 1.2f * (aspect >= 1.0f ? aspect : 1.0f);
|
float xRange = settings.baseSpawnRange * 1.2f * (aspect >= 1.0f ? aspect : 1.0f);
|
||||||
float yRange = settings.baseSpawnRange * 1.2f * (aspect >= 1.0f ? 1.0f : (1.0f / normalizedAspect));
|
float yRange = settings.baseSpawnRange * 1.2f * (aspect >= 1.0f ? 1.0f : (1.0f / normalizedAspect));
|
||||||
comet.x = randomRange(-xRange, xRange);
|
// Avoid spawning comets exactly on (or extremely near) the view axis,
|
||||||
comet.y = randomRange(-yRange, yRange);
|
// which can project to a nearly static bright dot.
|
||||||
|
const float axisMinFrac = 0.06f;
|
||||||
|
bool axisOk = false;
|
||||||
|
for (int attempt = 0; attempt < 10 && !axisOk; ++attempt) {
|
||||||
|
comet.x = randomRange(-xRange, xRange);
|
||||||
|
comet.y = randomRange(-yRange, yRange);
|
||||||
|
float nx = comet.x / std::max(xRange, 0.0001f);
|
||||||
|
float ny = comet.y / std::max(yRange, 0.0001f);
|
||||||
|
axisOk = (nx * nx + ny * ny) >= (axisMinFrac * axisMinFrac);
|
||||||
|
}
|
||||||
|
if (!axisOk) {
|
||||||
|
float ang = randomRange(0.0f, 6.28318530718f);
|
||||||
|
comet.x = std::cos(ang) * xRange * axisMinFrac;
|
||||||
|
comet.y = std::sin(ang) * yRange * axisMinFrac;
|
||||||
|
}
|
||||||
comet.z = randomRange(minDepth + 4.0f, maxDepth);
|
comet.z = randomRange(minDepth + 4.0f, maxDepth);
|
||||||
float baseSpeed = randomRange(settings.minSpeed, settings.maxSpeed);
|
float baseSpeed = randomRange(settings.minSpeed, settings.maxSpeed);
|
||||||
float multiplier = randomRange(settings.cometSpeedMultiplierMin, settings.cometSpeedMultiplierMax);
|
float multiplier = randomRange(settings.cometSpeedMultiplierMin, settings.cometSpeedMultiplierMax);
|
||||||
@ -154,9 +168,24 @@ void SpaceWarp::respawn(WarpStar& star, bool randomDepth) {
|
|||||||
float normalizedAspect = std::max(aspect, MIN_ASPECT);
|
float normalizedAspect = std::max(aspect, MIN_ASPECT);
|
||||||
float xRange = settings.baseSpawnRange * (aspect >= 1.0f ? aspect : 1.0f);
|
float xRange = settings.baseSpawnRange * (aspect >= 1.0f ? aspect : 1.0f);
|
||||||
float yRange = settings.baseSpawnRange * (aspect >= 1.0f ? 1.0f : (1.0f / normalizedAspect));
|
float yRange = settings.baseSpawnRange * (aspect >= 1.0f ? 1.0f : (1.0f / normalizedAspect));
|
||||||
star.x = randomRange(-xRange, xRange);
|
// Avoid axis-aligned stars (x≈0,y≈0) which can project to a static, bright center dot.
|
||||||
star.y = randomRange(-yRange, yRange);
|
const float axisMinFrac = 0.06f;
|
||||||
star.z = randomDepth ? randomRange(minDepth, maxDepth) : maxDepth;
|
bool axisOk = false;
|
||||||
|
for (int attempt = 0; attempt < 10 && !axisOk; ++attempt) {
|
||||||
|
star.x = randomRange(-xRange, xRange);
|
||||||
|
star.y = randomRange(-yRange, yRange);
|
||||||
|
float nx = star.x / std::max(xRange, 0.0001f);
|
||||||
|
float ny = star.y / std::max(yRange, 0.0001f);
|
||||||
|
axisOk = (nx * nx + ny * ny) >= (axisMinFrac * axisMinFrac);
|
||||||
|
}
|
||||||
|
if (!axisOk) {
|
||||||
|
float ang = randomRange(0.0f, 6.28318530718f);
|
||||||
|
star.x = std::cos(ang) * xRange * axisMinFrac;
|
||||||
|
star.y = std::sin(ang) * yRange * axisMinFrac;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep z slightly above minDepth so projection never starts from the exact singular plane.
|
||||||
|
star.z = randomDepth ? randomRange(minDepth + 0.25f, maxDepth) : maxDepth;
|
||||||
star.speed = randomRange(settings.minSpeed, settings.maxSpeed);
|
star.speed = randomRange(settings.minSpeed, settings.maxSpeed);
|
||||||
star.shade = randomRange(settings.minShade, settings.maxShade);
|
star.shade = randomRange(settings.minShade, settings.maxShade);
|
||||||
static constexpr Uint8 GRAY_SHADES[] = {160, 180, 200, 220, 240};
|
static constexpr Uint8 GRAY_SHADES[] = {160, 180, 200, 220, 240};
|
||||||
@ -253,6 +282,13 @@ void SpaceWarp::update(float deltaSeconds) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a star projects to (near) the visual center, it can appear perfectly static
|
||||||
|
// during straight-line flight. Replace it to avoid the "big static star" artifact.
|
||||||
|
if (std::abs(sx - centerX) < 1.25f && std::abs(sy - centerY) < 1.25f) {
|
||||||
|
respawn(star, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
star.prevScreenX = star.screenX;
|
star.prevScreenX = star.screenX;
|
||||||
star.prevScreenY = star.screenY;
|
star.prevScreenY = star.screenY;
|
||||||
star.screenX = sx;
|
star.screenX = sx;
|
||||||
|
|||||||
@ -68,9 +68,24 @@ void Starfield3D::setRandomDirection(Star3D& star) {
|
|||||||
|
|
||||||
void Starfield3D::updateStar(int index) {
|
void Starfield3D::updateStar(int index) {
|
||||||
Star3D& star = stars[index];
|
Star3D& star = stars[index];
|
||||||
|
|
||||||
star.x = randomFloat(-25.0f, 25.0f);
|
// Avoid spawning stars on (or very near) the view axis. A star with x≈0 and y≈0
|
||||||
star.y = randomFloat(-25.0f, 25.0f);
|
// projects to the exact center, and when it happens to be bright it looks like a
|
||||||
|
// static "big" star.
|
||||||
|
constexpr float SPAWN_RANGE = 25.0f;
|
||||||
|
constexpr float MIN_AXIS_RADIUS = 2.5f; // in star-space units
|
||||||
|
for (int attempt = 0; attempt < 8; ++attempt) {
|
||||||
|
star.x = randomFloat(-SPAWN_RANGE, SPAWN_RANGE);
|
||||||
|
star.y = randomFloat(-SPAWN_RANGE, SPAWN_RANGE);
|
||||||
|
if ((star.x * star.x + star.y * star.y) >= (MIN_AXIS_RADIUS * MIN_AXIS_RADIUS)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we somehow still ended up too close, push it out deterministically.
|
||||||
|
if ((star.x * star.x + star.y * star.y) < (MIN_AXIS_RADIUS * MIN_AXIS_RADIUS)) {
|
||||||
|
star.x = (star.x < 0.0f ? -1.0f : 1.0f) * MIN_AXIS_RADIUS;
|
||||||
|
star.y = (star.y < 0.0f ? -1.0f : 1.0f) * MIN_AXIS_RADIUS;
|
||||||
|
}
|
||||||
star.z = randomFloat(1.0f, MAX_DEPTH);
|
star.z = randomFloat(1.0f, MAX_DEPTH);
|
||||||
|
|
||||||
// Give stars initial velocities in all possible directions
|
// Give stars initial velocities in all possible directions
|
||||||
@ -91,6 +106,15 @@ void Starfield3D::updateStar(int index) {
|
|||||||
star.vz = -STAR_SPEED * randomFloat(0.8f, 1.2f);
|
star.vz = -STAR_SPEED * randomFloat(0.8f, 1.2f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure newly spawned stars have some lateral drift so they don't appear to
|
||||||
|
// "stick" near the center line.
|
||||||
|
if (std::abs(star.vx) < 0.02f && std::abs(star.vy) < 0.02f) {
|
||||||
|
const float sx = (star.x < 0.0f ? -1.0f : 1.0f);
|
||||||
|
const float sy = (star.y < 0.0f ? -1.0f : 1.0f);
|
||||||
|
star.vx = sx * randomFloat(0.04f, 0.14f);
|
||||||
|
star.vy = sy * randomFloat(0.04f, 0.14f);
|
||||||
|
}
|
||||||
|
|
||||||
star.targetVx = star.vx;
|
star.targetVx = star.vx;
|
||||||
star.targetVy = star.vy;
|
star.targetVy = star.vy;
|
||||||
|
|||||||
@ -1001,6 +1001,10 @@ int main(int, char **)
|
|||||||
// HELP - show inline help HUD in the MenuState
|
// HELP - show inline help HUD in the MenuState
|
||||||
if (menuState) menuState->showHelpPanel(true);
|
if (menuState) menuState->showHelpPanel(true);
|
||||||
break;
|
break;
|
||||||
|
case ui::BottomMenuItem::About:
|
||||||
|
// ABOUT - show inline about HUD in the MenuState
|
||||||
|
if (menuState) menuState->showAboutPanel(true);
|
||||||
|
break;
|
||||||
case ui::BottomMenuItem::Exit:
|
case ui::BottomMenuItem::Exit:
|
||||||
showExitConfirmPopup = true;
|
showExitConfirmPopup = true;
|
||||||
exitPopupSelectedButton = 1;
|
exitPopupSelectedButton = 1;
|
||||||
@ -1792,6 +1796,9 @@ int main(int, char **)
|
|||||||
drawH
|
drawH
|
||||||
};
|
};
|
||||||
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
||||||
|
// Use linear filtering for the scaled overlay to avoid single-pixel aliasing
|
||||||
|
// artifacts (e.g. a tiny static dot) when the PNG is resized.
|
||||||
|
SDL_SetTextureScaleMode(mainScreenTex, SDL_SCALEMODE_LINEAR);
|
||||||
SDL_RenderTexture(renderer, mainScreenTex, nullptr, &dst);
|
SDL_RenderTexture(renderer, mainScreenTex, nullptr, &dst);
|
||||||
}
|
}
|
||||||
SDL_SetRenderViewport(renderer, &logicalVP);
|
SDL_SetRenderViewport(renderer, &logicalVP);
|
||||||
|
|||||||
@ -112,6 +112,11 @@ MenuState::MenuState(StateContext& ctx) : State(ctx) {}
|
|||||||
void MenuState::showHelpPanel(bool show) {
|
void MenuState::showHelpPanel(bool show) {
|
||||||
if (show) {
|
if (show) {
|
||||||
if (!helpPanelVisible && !helpPanelAnimating) {
|
if (!helpPanelVisible && !helpPanelAnimating) {
|
||||||
|
// Avoid overlapping panels
|
||||||
|
if (aboutPanelVisible && !aboutPanelAnimating) {
|
||||||
|
aboutPanelAnimating = true;
|
||||||
|
aboutDirection = -1;
|
||||||
|
}
|
||||||
helpPanelAnimating = true;
|
helpPanelAnimating = true;
|
||||||
helpDirection = 1;
|
helpDirection = 1;
|
||||||
helpScroll = 0.0;
|
helpScroll = 0.0;
|
||||||
@ -124,6 +129,38 @@ void MenuState::showHelpPanel(bool show) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MenuState::showAboutPanel(bool show) {
|
||||||
|
if (show) {
|
||||||
|
if (!aboutPanelVisible && !aboutPanelAnimating) {
|
||||||
|
// Avoid overlapping panels
|
||||||
|
if (helpPanelVisible && !helpPanelAnimating) {
|
||||||
|
helpPanelAnimating = true;
|
||||||
|
helpDirection = -1;
|
||||||
|
}
|
||||||
|
if (optionsVisible && !optionsAnimating) {
|
||||||
|
optionsAnimating = true;
|
||||||
|
optionsDirection = -1;
|
||||||
|
}
|
||||||
|
if (levelPanelVisible && !levelPanelAnimating) {
|
||||||
|
levelPanelAnimating = true;
|
||||||
|
levelDirection = -1;
|
||||||
|
}
|
||||||
|
if (exitPanelVisible && !exitPanelAnimating) {
|
||||||
|
exitPanelAnimating = true;
|
||||||
|
exitDirection = -1;
|
||||||
|
if (ctx.showExitConfirmPopup) *ctx.showExitConfirmPopup = false;
|
||||||
|
}
|
||||||
|
aboutPanelAnimating = true;
|
||||||
|
aboutDirection = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (aboutPanelVisible && !aboutPanelAnimating) {
|
||||||
|
aboutPanelAnimating = true;
|
||||||
|
aboutDirection = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MenuState::onEnter() {
|
void MenuState::onEnter() {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState::onEnter called");
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "MenuState::onEnter called");
|
||||||
if (ctx.showExitConfirmPopup) {
|
if (ctx.showExitConfirmPopup) {
|
||||||
@ -349,6 +386,26 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the inline about HUD is visible and not animating, capture navigation
|
||||||
|
if (aboutPanelVisible && !aboutPanelAnimating) {
|
||||||
|
switch (e.key.scancode) {
|
||||||
|
case SDL_SCANCODE_ESCAPE:
|
||||||
|
case SDL_SCANCODE_RETURN:
|
||||||
|
case SDL_SCANCODE_KP_ENTER:
|
||||||
|
case SDL_SCANCODE_SPACE:
|
||||||
|
aboutPanelAnimating = true; aboutDirection = -1;
|
||||||
|
return;
|
||||||
|
case SDL_SCANCODE_LEFT:
|
||||||
|
case SDL_SCANCODE_RIGHT:
|
||||||
|
case SDL_SCANCODE_UP:
|
||||||
|
case SDL_SCANCODE_DOWN:
|
||||||
|
aboutPanelAnimating = true; aboutDirection = -1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If inline level HUD visible and not animating, capture navigation
|
// If inline level HUD visible and not animating, capture navigation
|
||||||
if (levelPanelVisible && !levelPanelAnimating) {
|
if (levelPanelVisible && !levelPanelAnimating) {
|
||||||
// Start navigation from tentative hover if present, otherwise from committed selection
|
// Start navigation from tentative hover if present, otherwise from committed selection
|
||||||
@ -385,7 +442,7 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
case SDL_SCANCODE_LEFT:
|
case SDL_SCANCODE_LEFT:
|
||||||
case SDL_SCANCODE_UP:
|
case SDL_SCANCODE_UP:
|
||||||
{
|
{
|
||||||
const int total = 5;
|
const int total = 6;
|
||||||
selectedButton = (selectedButton + total - 1) % total;
|
selectedButton = (selectedButton + total - 1) % total;
|
||||||
// brief bright flash on navigation
|
// brief bright flash on navigation
|
||||||
buttonFlash = 1.0;
|
buttonFlash = 1.0;
|
||||||
@ -394,7 +451,7 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
case SDL_SCANCODE_RIGHT:
|
case SDL_SCANCODE_RIGHT:
|
||||||
case SDL_SCANCODE_DOWN:
|
case SDL_SCANCODE_DOWN:
|
||||||
{
|
{
|
||||||
const int total = 5;
|
const int total = 6;
|
||||||
selectedButton = (selectedButton + 1) % total;
|
selectedButton = (selectedButton + 1) % total;
|
||||||
// brief bright flash on navigation
|
// brief bright flash on navigation
|
||||||
buttonFlash = 1.0;
|
buttonFlash = 1.0;
|
||||||
@ -444,6 +501,16 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
|
// Toggle the inline ABOUT HUD (show/hide)
|
||||||
|
if (!aboutPanelVisible && !aboutPanelAnimating) {
|
||||||
|
aboutPanelAnimating = true;
|
||||||
|
aboutDirection = 1;
|
||||||
|
} else if (aboutPanelVisible && !aboutPanelAnimating) {
|
||||||
|
aboutPanelAnimating = true;
|
||||||
|
aboutDirection = -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
// Show the inline exit HUD
|
// Show the inline exit HUD
|
||||||
if (!exitPanelVisible && !exitPanelAnimating) {
|
if (!exitPanelVisible && !exitPanelAnimating) {
|
||||||
exitPanelAnimating = true;
|
exitPanelAnimating = true;
|
||||||
@ -540,6 +607,21 @@ void MenuState::update(double frameMs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advance about panel animation if active
|
||||||
|
if (aboutPanelAnimating) {
|
||||||
|
double delta = (frameMs / aboutTransitionDurationMs) * static_cast<double>(aboutDirection);
|
||||||
|
aboutTransition += delta;
|
||||||
|
if (aboutTransition >= 1.0) {
|
||||||
|
aboutTransition = 1.0;
|
||||||
|
aboutPanelVisible = true;
|
||||||
|
aboutPanelAnimating = false;
|
||||||
|
} else if (aboutTransition <= 0.0) {
|
||||||
|
aboutTransition = 0.0;
|
||||||
|
aboutPanelVisible = false;
|
||||||
|
aboutPanelAnimating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Animate level selection highlight position toward the selected cell center
|
// Animate level selection highlight position toward the selected cell center
|
||||||
if (levelTransition > 0.0 && (lastLogicalScale > 0.0f)) {
|
if (levelTransition > 0.0 && (lastLogicalScale > 0.0f)) {
|
||||||
// Recompute same grid geometry used in render to find target center
|
// Recompute same grid geometry used in render to find target center
|
||||||
@ -665,14 +747,18 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
const float moveAmount = 420.0f; // increased so lower score rows slide further up
|
const float moveAmount = 420.0f; // increased so lower score rows slide further up
|
||||||
|
|
||||||
// Compute eased transition and delta to shift highscores when either options, level, or exit HUD is shown.
|
// 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(std::max(optionsTransition, levelTransition), exitTransition), helpTransition));
|
float combinedTransition = static_cast<float>(std::max(
|
||||||
|
std::max(std::max(optionsTransition, levelTransition), exitTransition),
|
||||||
|
std::max(helpTransition, aboutTransition)
|
||||||
|
));
|
||||||
float eased = combinedTransition * combinedTransition * (3.0f - 2.0f * combinedTransition); // cubic smoothstep
|
float eased = combinedTransition * combinedTransition * (3.0f - 2.0f * combinedTransition); // cubic smoothstep
|
||||||
float panelDelta = eased * moveAmount;
|
float panelDelta = eased * moveAmount;
|
||||||
|
|
||||||
// Draw a larger centered logo above the highscores area, then a small "TOP PLAYER" label
|
// Draw a larger centered logo above the highscores area, then a small "TOP PLAYER" label
|
||||||
// Move logo a bit lower for better spacing
|
// Move the whole block slightly up to better match the main screen overlay framing.
|
||||||
float menuYOffset = LOGICAL_H * 0.03f; // same offset used for buttons
|
float menuYOffset = LOGICAL_H * 0.03f; // same offset used for buttons
|
||||||
float topPlayersY = LOGICAL_H * 0.20f + contentOffsetY - panelDelta + menuYOffset;
|
float scoresYOffset = -LOGICAL_H * 0.05f;
|
||||||
|
float topPlayersY = LOGICAL_H * 0.20f + contentOffsetY - panelDelta + menuYOffset + scoresYOffset;
|
||||||
float scoresStartY = topPlayersY;
|
float scoresStartY = topPlayersY;
|
||||||
if (useFont) {
|
if (useFont) {
|
||||||
// Preferred logo texture (full) if present, otherwise the small logo
|
// Preferred logo texture (full) if present, otherwise the small logo
|
||||||
@ -1185,8 +1271,47 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
int w=0,h=0; f->measure(entry.description, 0.62f, w, h);
|
int w=0,h=0; f->measure(entry.description, 0.62f, w, h);
|
||||||
cursorY += static_cast<float>(h) + 16.0f;
|
cursorY += static_cast<float>(h) + 16.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (rest of help render continues below)
|
||||||
// Add a larger gap between sections
|
// Add a larger gap between sections
|
||||||
cursorY += 22.0f;
|
cursorY += 22.0f;
|
||||||
|
|
||||||
|
// Draw inline ABOUT HUD (no boxed background) — simple main info
|
||||||
|
if (aboutTransition > 0.0) {
|
||||||
|
float easedA = static_cast<float>(aboutTransition);
|
||||||
|
easedA = easedA * easedA * (3.0f - 2.0f * easedA);
|
||||||
|
const float PW = std::min(520.0f, LOGICAL_W * 0.65f);
|
||||||
|
const float PH = std::min(320.0f, LOGICAL_H * 0.60f);
|
||||||
|
float panelBaseX = (LOGICAL_W - PW) * 0.5f + contentOffsetX;
|
||||||
|
float panelBaseY = (LOGICAL_H - PH) * 0.5f + contentOffsetY - (LOGICAL_H * 0.10f);
|
||||||
|
float slideAmount = LOGICAL_H * 0.42f;
|
||||||
|
float panelY = panelBaseY + (1.0f - easedA) * slideAmount;
|
||||||
|
|
||||||
|
FontAtlas* f = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
||||||
|
if (f) {
|
||||||
|
f->draw(renderer, panelBaseX + 12.0f, panelY + 6.0f, "ABOUT", 1.25f, SDL_Color{255,220,0,255});
|
||||||
|
|
||||||
|
float x = panelBaseX + 16.0f;
|
||||||
|
float y = panelY + 52.0f;
|
||||||
|
const float lineGap = 30.0f;
|
||||||
|
const SDL_Color textCol{200, 210, 230, 255};
|
||||||
|
const SDL_Color keyCol{255, 255, 255, 255};
|
||||||
|
|
||||||
|
f->draw(renderer, x, y, "SDL3 TETRIS", 1.05f, keyCol); y += lineGap;
|
||||||
|
f->draw(renderer, x, y, "C++20 / SDL3 / SDL3_ttf", 0.80f, textCol); y += lineGap + 6.0f;
|
||||||
|
|
||||||
|
f->draw(renderer, x, y, "GAMEPLAY", 0.85f, SDL_Color{180,200,255,255}); y += lineGap;
|
||||||
|
f->draw(renderer, x, y, "H Hold / swap current piece", 0.78f, textCol); y += lineGap;
|
||||||
|
f->draw(renderer, x, y, "SPACE Hard drop", 0.78f, textCol); y += lineGap;
|
||||||
|
f->draw(renderer, x, y, "P Pause", 0.78f, textCol); y += lineGap + 6.0f;
|
||||||
|
|
||||||
|
f->draw(renderer, x, y, "UI", 0.85f, SDL_Color{180,200,255,255}); y += lineGap;
|
||||||
|
f->draw(renderer, x, y, "F1 Toggle help overlay", 0.78f, textCol); y += lineGap;
|
||||||
|
f->draw(renderer, x, y, "ESC Back / exit prompt", 0.78f, textCol); y += lineGap + 10.0f;
|
||||||
|
|
||||||
|
f->draw(renderer, x, y, "PRESS ESC OR ARROW KEYS TO RETURN", 0.75f, SDL_Color{215,220,240,255});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
float leftCursor = panelY + 48.0f - static_cast<float>(helpScroll);
|
float leftCursor = panelY + 48.0f - static_cast<float>(helpScroll);
|
||||||
|
|||||||
@ -17,9 +17,11 @@ public:
|
|||||||
void renderMainButtonTop(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP);
|
void renderMainButtonTop(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP);
|
||||||
// Show or hide the inline HELP panel (menu-style)
|
// Show or hide the inline HELP panel (menu-style)
|
||||||
void showHelpPanel(bool show);
|
void showHelpPanel(bool show);
|
||||||
|
// Show or hide the inline ABOUT panel (menu-style)
|
||||||
|
void showAboutPanel(bool show);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int selectedButton = 0; // 0 = PLAY, 1 = LEVEL, 2 = OPTIONS, 3 = HELP, 4 = EXIT
|
int selectedButton = 0; // 0 = PLAY, 1 = LEVEL, 2 = OPTIONS, 3 = HELP, 4 = ABOUT, 5 = EXIT
|
||||||
|
|
||||||
// Button icons (optional - will use text if nullptr)
|
// Button icons (optional - will use text if nullptr)
|
||||||
SDL_Texture* playIcon = nullptr;
|
SDL_Texture* playIcon = nullptr;
|
||||||
@ -85,4 +87,11 @@ private:
|
|||||||
double helpTransitionDurationMs = 360.0;
|
double helpTransitionDurationMs = 360.0;
|
||||||
int helpDirection = 1; // 1 show, -1 hide
|
int helpDirection = 1; // 1 show, -1 hide
|
||||||
double helpScroll = 0.0; // vertical scroll offset for content
|
double helpScroll = 0.0; // vertical scroll offset for content
|
||||||
|
|
||||||
|
// About submenu (inline HUD like Help)
|
||||||
|
bool aboutPanelVisible = false;
|
||||||
|
bool aboutPanelAnimating = false;
|
||||||
|
double aboutTransition = 0.0; // 0..1
|
||||||
|
double aboutTransitionDurationMs = 360.0;
|
||||||
|
int aboutDirection = 1; // 1 show, -1 hide
|
||||||
};
|
};
|
||||||
|
|||||||
@ -25,7 +25,8 @@ BottomMenu buildBottomMenu(const MenuLayoutParams& params, int startLevel) {
|
|||||||
menu.buttons[1] = Button{ BottomMenuItem::Level, rects[1], levelBtnText, true };
|
menu.buttons[1] = Button{ BottomMenuItem::Level, rects[1], levelBtnText, true };
|
||||||
menu.buttons[2] = Button{ BottomMenuItem::Options, rects[2], "OPTIONS", true };
|
menu.buttons[2] = Button{ BottomMenuItem::Options, rects[2], "OPTIONS", true };
|
||||||
menu.buttons[3] = Button{ BottomMenuItem::Help, rects[3], "HELP", true };
|
menu.buttons[3] = Button{ BottomMenuItem::Help, rects[3], "HELP", true };
|
||||||
menu.buttons[4] = Button{ BottomMenuItem::Exit, rects[4], "EXIT", true };
|
menu.buttons[4] = Button{ BottomMenuItem::About, rects[4], "ABOUT", true };
|
||||||
|
menu.buttons[5] = Button{ BottomMenuItem::Exit, rects[5], "EXIT", true };
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
@ -73,15 +74,17 @@ void renderBottomMenu(SDL_Renderer* renderer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// '+' separators between the bottom HUD buttons (indices 1..4)
|
// '+' separators between the bottom HUD buttons (indices 1..last)
|
||||||
{
|
{
|
||||||
SDL_BlendMode prevBlend = SDL_BLENDMODE_NONE;
|
SDL_BlendMode prevBlend = SDL_BLENDMODE_NONE;
|
||||||
SDL_GetRenderDrawBlendMode(renderer, &prevBlend);
|
SDL_GetRenderDrawBlendMode(renderer, &prevBlend);
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
SDL_SetRenderDrawColor(renderer, 120, 220, 255, static_cast<Uint8>(std::round(180.0 * baseMul)));
|
SDL_SetRenderDrawColor(renderer, 120, 220, 255, static_cast<Uint8>(std::round(180.0 * baseMul)));
|
||||||
|
|
||||||
float y = menu.buttons[1].rect.y + menu.buttons[1].rect.h * 0.5f;
|
const int firstSmall = 1;
|
||||||
for (int i = 1; i < 4; ++i) {
|
const int lastSmall = MENU_BTN_COUNT - 1;
|
||||||
|
float y = menu.buttons[firstSmall].rect.y + menu.buttons[firstSmall].rect.h * 0.5f;
|
||||||
|
for (int i = firstSmall; i < lastSmall; ++i) {
|
||||||
float x = (menu.buttons[i].rect.x + menu.buttons[i].rect.w + menu.buttons[i + 1].rect.x) * 0.5f;
|
float x = (menu.buttons[i].rect.x + menu.buttons[i].rect.w + menu.buttons[i + 1].rect.x) * 0.5f;
|
||||||
SDL_RenderLine(renderer, x - 4.0f, y, x + 4.0f, y);
|
SDL_RenderLine(renderer, x - 4.0f, y, x + 4.0f, y);
|
||||||
SDL_RenderLine(renderer, x, y - 4.0f, x, y + 4.0f);
|
SDL_RenderLine(renderer, x, y - 4.0f, x, y + 4.0f);
|
||||||
|
|||||||
@ -18,7 +18,8 @@ enum class BottomMenuItem : int {
|
|||||||
Level = 1,
|
Level = 1,
|
||||||
Options = 2,
|
Options = 2,
|
||||||
Help = 3,
|
Help = 3,
|
||||||
Exit = 4,
|
About = 4,
|
||||||
|
Exit = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Button {
|
struct Button {
|
||||||
@ -35,8 +36,8 @@ struct BottomMenu {
|
|||||||
BottomMenu buildBottomMenu(const MenuLayoutParams& params, int startLevel);
|
BottomMenu buildBottomMenu(const MenuLayoutParams& params, int startLevel);
|
||||||
|
|
||||||
// Draws the cockpit HUD menu (PLAY + 4 bottom items) using existing UIRenderer primitives.
|
// Draws the cockpit HUD menu (PLAY + 4 bottom items) using existing UIRenderer primitives.
|
||||||
// hoveredIndex: -1..4
|
// hoveredIndex: -1..5
|
||||||
// selectedIndex: 0..4 (keyboard selection)
|
// selectedIndex: 0..5 (keyboard selection)
|
||||||
// alphaMul: 0..1 (overall group alpha)
|
// alphaMul: 0..1 (overall group alpha)
|
||||||
void renderBottomMenu(SDL_Renderer* renderer,
|
void renderBottomMenu(SDL_Renderer* renderer,
|
||||||
FontAtlas* font,
|
FontAtlas* font,
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
std::array<SDL_FRect, 5> computeMenuButtonRects(const MenuLayoutParams& p) {
|
std::array<SDL_FRect, MENU_BTN_COUNT> computeMenuButtonRects(const MenuLayoutParams& p) {
|
||||||
const float LOGICAL_W = static_cast<float>(p.logicalW);
|
const float LOGICAL_W = static_cast<float>(p.logicalW);
|
||||||
const float LOGICAL_H = static_cast<float>(p.logicalH);
|
const float LOGICAL_H = static_cast<float>(p.logicalH);
|
||||||
float contentOffsetX = (p.winW - LOGICAL_W * p.logicalScale) * 0.5f / p.logicalScale;
|
float contentOffsetX = (p.winW - LOGICAL_W * p.logicalScale) * 0.5f / p.logicalScale;
|
||||||
@ -13,7 +13,7 @@ std::array<SDL_FRect, 5> computeMenuButtonRects(const MenuLayoutParams& p) {
|
|||||||
|
|
||||||
// Cockpit HUD layout (matches main_screen art):
|
// Cockpit HUD layout (matches main_screen art):
|
||||||
// - A big centered PLAY button
|
// - A big centered PLAY button
|
||||||
// - A second row of 4 smaller buttons: LEVEL / OPTIONS / HELP / EXIT
|
// - A second row of 5 smaller buttons: LEVEL / OPTIONS / HELP / ABOUT / EXIT
|
||||||
const float marginX = std::max(24.0f, LOGICAL_W * 0.03f);
|
const float marginX = std::max(24.0f, LOGICAL_W * 0.03f);
|
||||||
const float marginBottom = std::max(26.0f, LOGICAL_H * 0.03f);
|
const float marginBottom = std::max(26.0f, LOGICAL_H * 0.03f);
|
||||||
const float availableW = std::max(120.0f, LOGICAL_W - marginX * 2.0f);
|
const float availableW = std::max(120.0f, LOGICAL_W - marginX * 2.0f);
|
||||||
@ -25,7 +25,8 @@ std::array<SDL_FRect, 5> computeMenuButtonRects(const MenuLayoutParams& p) {
|
|||||||
float smallSpacing = 28.0f;
|
float smallSpacing = 28.0f;
|
||||||
|
|
||||||
// Scale down for narrow windows so nothing goes offscreen.
|
// Scale down for narrow windows so nothing goes offscreen.
|
||||||
float smallTotal = smallW * 4.0f + smallSpacing * 3.0f;
|
const int smallCount = MENU_BTN_COUNT - 1;
|
||||||
|
float smallTotal = smallW * static_cast<float>(smallCount) + smallSpacing * static_cast<float>(smallCount - 1);
|
||||||
if (smallTotal > availableW) {
|
if (smallTotal > availableW) {
|
||||||
float s = availableW / smallTotal;
|
float s = availableW / smallTotal;
|
||||||
smallW *= s;
|
smallW *= s;
|
||||||
@ -45,14 +46,14 @@ std::array<SDL_FRect, 5> computeMenuButtonRects(const MenuLayoutParams& p) {
|
|||||||
std::array<SDL_FRect, MENU_BTN_COUNT> rects{};
|
std::array<SDL_FRect, MENU_BTN_COUNT> rects{};
|
||||||
rects[0] = SDL_FRect{ centerX - playW * 0.5f, playCY - playH * 0.5f, playW, playH };
|
rects[0] = SDL_FRect{ centerX - playW * 0.5f, playCY - playH * 0.5f, playW, playH };
|
||||||
|
|
||||||
float rowW = smallW * 4.0f + smallSpacing * 3.0f;
|
float rowW = smallW * static_cast<float>(smallCount) + smallSpacing * static_cast<float>(smallCount - 1);
|
||||||
float left = centerX - rowW * 0.5f;
|
float left = centerX - rowW * 0.5f;
|
||||||
float minLeft = contentOffsetX + marginX;
|
float minLeft = contentOffsetX + marginX;
|
||||||
float maxRight = contentOffsetX + LOGICAL_W - marginX;
|
float maxRight = contentOffsetX + LOGICAL_W - marginX;
|
||||||
if (left < minLeft) left = minLeft;
|
if (left < minLeft) left = minLeft;
|
||||||
if (left + rowW > maxRight) left = std::max(minLeft, maxRight - rowW);
|
if (left + rowW > maxRight) left = std::max(minLeft, maxRight - rowW);
|
||||||
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < smallCount; ++i) {
|
||||||
float x = left + i * (smallW + smallSpacing);
|
float x = left + i * (smallW + smallSpacing);
|
||||||
rects[i + 1] = SDL_FRect{ x, smallCY - smallH * 0.5f, smallW, smallH };
|
rects[i + 1] = SDL_FRect{ x, smallCY - smallH * 0.5f, smallW, smallH };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
static constexpr int MENU_BTN_COUNT = 5;
|
static constexpr int MENU_BTN_COUNT = 6;
|
||||||
static constexpr float MENU_SMALL_THRESHOLD = 700.0f;
|
static constexpr float MENU_SMALL_THRESHOLD = 700.0f;
|
||||||
static constexpr float MENU_BTN_WIDTH_LARGE = 300.0f;
|
static constexpr float MENU_BTN_WIDTH_LARGE = 300.0f;
|
||||||
static constexpr float MENU_BTN_WIDTH_SMALL_FACTOR = 0.4f; // multiplied by LOGICAL_W
|
static constexpr float MENU_BTN_WIDTH_SMALL_FACTOR = 0.4f; // multiplied by LOGICAL_W
|
||||||
|
|||||||
Reference in New Issue
Block a user