feat: implement textured line clear effects and refine UI alignment

- **Visual Effects**: Upgraded line clear particles to use the game's block texture instead of simple circles, matching the reference web game's aesthetic.
- **Particle Physics**: Tuned particle velocity, gravity, and fade rates for a more dynamic explosion effect.
- **Rendering Integration**: Updated [main.cpp](cci:7://file:///d:/Sites/Work/tetris/src/main.cpp:0:0-0:0) and `GameRenderer` to pass the block texture to the effect system and correctly trigger animations upon line completion.
- **Menu UI**: Fixed [MenuState](cci:1://file:///d:/Sites/Work/tetris/src/states/MenuState.cpp:19:0-19:55) layout calculations to use fixed logical dimensions (1200x1000), ensuring consistent centering and alignment of the logo, buttons, and settings icon across different window sizes.
- **Code Cleanup**: Refactored `PlayingState` to delegate effect triggering to the rendering layer where correct screen coordinates are available.
This commit is contained in:
2025-11-21 21:19:14 +01:00
parent b5ef9172b3
commit 66099809e0
47 changed files with 5547 additions and 267 deletions

View File

@ -1,16 +1,15 @@
// LevelSelectorState.cpp - Level selection popup state implementation
#include "LevelSelectorState.h"
#include "State.h"
#include "../core/StateManager.h"
#include "../graphics/Font.h"
#include "../core/state/StateManager.h"
#include "../core/GlobalState.h"
#include "../graphics/ui/Font.h"
#include <SDL3/SDL.h>
#include <algorithm>
#include <cstdio>
#include <string>
// Constants from main.cpp
static constexpr int LOGICAL_W = 1200;
static constexpr int LOGICAL_H = 1000;
// Use dynamic logical dimensions from GlobalState instead of hardcoded values
// --- Minimal draw helpers and look-and-feel adapted from the sample ---
static inline SDL_Color RGBA(Uint8 r, Uint8 g, Uint8 b, Uint8 a = 255) { return SDL_Color{r, g, b, a}; }
@ -187,6 +186,10 @@ void LevelSelectorState::handleEvent(const SDL_Event& e) {
if (ctx.startLevelSelection) *ctx.startLevelSelection = hoveredLevel;
} else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
if (e.button.button == SDL_BUTTON_LEFT) {
// Get dynamic logical dimensions
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
// convert mouse to logical coords (viewport is already centered)
float lx = (float(e.button.x) - float(lastLogicalVP.x)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
float ly = (float(e.button.y) - float(lastLogicalVP.y)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
@ -200,6 +203,10 @@ void LevelSelectorState::handleEvent(const SDL_Event& e) {
}
}
} else if (e.type == SDL_EVENT_MOUSE_MOTION) {
// Get dynamic logical dimensions
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
// convert mouse to logical coords (viewport is already centered)
float lx = (float(e.motion.x) - float(lastLogicalVP.x)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
float ly = (float(e.motion.y) - float(lastLogicalVP.y)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
@ -224,6 +231,10 @@ void LevelSelectorState::render(SDL_Renderer* renderer, float logicalScale, SDL_
void LevelSelectorState::drawLevelSelectionPopup(SDL_Renderer* renderer) {
if (!renderer) return;
// Get dynamic logical dimensions
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
// Since ApplicationManager sets up a centered viewport, we draw directly in logical coordinates
// The viewport (LOGICAL_W x LOGICAL_H) is already centered within the window
float vw = float(LOGICAL_W);
@ -252,6 +263,10 @@ void LevelSelectorState::drawLevelSelectionPopup(SDL_Renderer* renderer) {
}
bool LevelSelectorState::isMouseInPopup(float mouseX, float mouseY, float& popupX, float& popupY, float& popupW, float& popupH) {
// Get dynamic logical dimensions
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
// Simplified: viewport is already centered, just convert mouse to logical coords
(void)mouseX; (void)mouseY;
float lx = 0.f, ly = 0.f;
@ -265,6 +280,10 @@ bool LevelSelectorState::isMouseInPopup(float mouseX, float mouseY, float& popup
}
int LevelSelectorState::getLevelFromMouse(float mouseX, float mouseY, float popupX, float popupY, float popupW, float popupH) {
// Get dynamic logical dimensions
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
(void)popupX; (void)popupY; (void)popupW; (void)popupH;
float lx = 0.f, ly = 0.f;
if (lastLogicalScale > 0.0f) {