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:
360
src/core/input/InputManager.cpp
Normal file
360
src/core/input/InputManager.cpp
Normal file
@ -0,0 +1,360 @@
|
||||
#include "InputManager.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
|
||||
InputManager::InputManager() {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "InputManager initialized");
|
||||
}
|
||||
|
||||
void InputManager::processEvents() {
|
||||
// Update previous state before processing new events
|
||||
updateInputState();
|
||||
|
||||
// Reset mouse delta
|
||||
m_mouseDeltaX = 0.0f;
|
||||
m_mouseDeltaY = 0.0f;
|
||||
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
// Trace every polled event type for debugging abrupt termination
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "InputManager: polled event type=%d\n", (int)event.type); fclose(f); }
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
m_shouldQuit = true;
|
||||
for (auto& handler : m_quitHandlers) {
|
||||
try {
|
||||
// Trace quit event handling
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "InputManager: SDL_EVENT_QUIT polled\n"); fclose(f); }
|
||||
handler();
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Exception in quit handler: %s", e.what());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
handleKeyEvent(event.key);
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
handleMouseButtonEvent(event.button);
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
handleMouseMotionEvent(event.motion);
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
case SDL_EVENT_WINDOW_MOVED:
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
handleWindowEvent(event.window);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unhandled event types
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::update(float deltaTime) {
|
||||
updateDAS(deltaTime);
|
||||
}
|
||||
|
||||
bool InputManager::isKeyPressed(SDL_Scancode key) const {
|
||||
auto current = m_currentKeyState.find(key);
|
||||
auto previous = m_previousKeyState.find(key);
|
||||
|
||||
bool currentlyPressed = (current != m_currentKeyState.end() && current->second);
|
||||
bool previouslyPressed = (previous != m_previousKeyState.end() && previous->second);
|
||||
|
||||
return currentlyPressed && !previouslyPressed;
|
||||
}
|
||||
|
||||
bool InputManager::isKeyReleased(SDL_Scancode key) const {
|
||||
auto current = m_currentKeyState.find(key);
|
||||
auto previous = m_previousKeyState.find(key);
|
||||
|
||||
bool currentlyPressed = (current != m_currentKeyState.end() && current->second);
|
||||
bool previouslyPressed = (previous != m_previousKeyState.end() && previous->second);
|
||||
|
||||
return !currentlyPressed && previouslyPressed;
|
||||
}
|
||||
|
||||
bool InputManager::isKeyHeld(SDL_Scancode key) const {
|
||||
auto it = m_currentKeyState.find(key);
|
||||
return (it != m_currentKeyState.end() && it->second);
|
||||
}
|
||||
|
||||
bool InputManager::isMouseButtonPressed(int button) const {
|
||||
auto current = m_currentMouseState.find(button);
|
||||
auto previous = m_previousMouseState.find(button);
|
||||
|
||||
bool currentlyPressed = (current != m_currentMouseState.end() && current->second);
|
||||
bool previouslyPressed = (previous != m_previousMouseState.end() && previous->second);
|
||||
|
||||
return currentlyPressed && !previouslyPressed;
|
||||
}
|
||||
|
||||
bool InputManager::isMouseButtonReleased(int button) const {
|
||||
auto current = m_currentMouseState.find(button);
|
||||
auto previous = m_previousMouseState.find(button);
|
||||
|
||||
bool currentlyPressed = (current != m_currentMouseState.end() && current->second);
|
||||
bool previouslyPressed = (previous != m_previousMouseState.end() && previous->second);
|
||||
|
||||
return !currentlyPressed && previouslyPressed;
|
||||
}
|
||||
|
||||
bool InputManager::isMouseButtonHeld(int button) const {
|
||||
auto it = m_currentMouseState.find(button);
|
||||
return (it != m_currentMouseState.end() && it->second);
|
||||
}
|
||||
|
||||
void InputManager::getMousePosition(float& x, float& y) const {
|
||||
x = m_mouseX;
|
||||
y = m_mouseY;
|
||||
}
|
||||
|
||||
void InputManager::getMouseDelta(float& deltaX, float& deltaY) const {
|
||||
deltaX = m_mouseDeltaX;
|
||||
deltaY = m_mouseDeltaY;
|
||||
}
|
||||
|
||||
void InputManager::registerKeyHandler(KeyHandler handler) {
|
||||
m_keyHandlers.push_back(std::move(handler));
|
||||
}
|
||||
|
||||
void InputManager::registerMouseButtonHandler(MouseButtonHandler handler) {
|
||||
m_mouseButtonHandlers.push_back(std::move(handler));
|
||||
}
|
||||
|
||||
void InputManager::registerMouseMotionHandler(MouseMotionHandler handler) {
|
||||
m_mouseMotionHandlers.push_back(std::move(handler));
|
||||
}
|
||||
|
||||
void InputManager::registerWindowEventHandler(WindowEventHandler handler) {
|
||||
m_windowEventHandlers.push_back(std::move(handler));
|
||||
}
|
||||
|
||||
void InputManager::registerQuitHandler(QuitHandler handler) {
|
||||
m_quitHandlers.push_back(std::move(handler));
|
||||
}
|
||||
|
||||
void InputManager::configureDAS(float delayMs, float repeatMs) {
|
||||
m_dasDelay = delayMs;
|
||||
m_dasRepeat = repeatMs;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "DAS configured: delay=%.1fms, repeat=%.1fms", delayMs, repeatMs);
|
||||
}
|
||||
|
||||
bool InputManager::checkDASMovement(SDL_Scancode leftKey, SDL_Scancode rightKey, int& direction) {
|
||||
bool leftHeld = isKeyHeld(leftKey);
|
||||
bool rightHeld = isKeyHeld(rightKey);
|
||||
|
||||
// Determine current direction
|
||||
int currentDirection = 0;
|
||||
if (leftHeld && !rightHeld) {
|
||||
currentDirection = -1;
|
||||
} else if (rightHeld && !leftHeld) {
|
||||
currentDirection = 1;
|
||||
}
|
||||
|
||||
// If no direction or direction changed, reset DAS
|
||||
if (currentDirection == 0 ||
|
||||
(m_dasState.isActive &&
|
||||
((currentDirection == -1 && m_dasState.activeKey != leftKey) ||
|
||||
(currentDirection == 1 && m_dasState.activeKey != rightKey)))) {
|
||||
resetDAS();
|
||||
direction = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for initial key press (immediate movement)
|
||||
if ((currentDirection == -1 && isKeyPressed(leftKey)) ||
|
||||
(currentDirection == 1 && isKeyPressed(rightKey))) {
|
||||
m_dasState.isActive = true;
|
||||
m_dasState.delayTimer = m_dasDelay;
|
||||
m_dasState.repeatTimer = 0.0f;
|
||||
m_dasState.activeKey = (currentDirection == -1) ? leftKey : rightKey;
|
||||
direction = currentDirection;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If DAS is active and delay/repeat timers expired, trigger movement
|
||||
if (m_dasState.isActive && m_dasState.delayTimer <= 0.0f && m_dasState.repeatTimer <= 0.0f) {
|
||||
m_dasState.repeatTimer = m_dasRepeat;
|
||||
direction = currentDirection;
|
||||
return true;
|
||||
}
|
||||
|
||||
direction = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
void InputManager::handleKeyEvent(const SDL_KeyboardEvent& event) {
|
||||
SDL_Scancode scancode = event.scancode;
|
||||
bool pressed = (event.type == SDL_EVENT_KEY_DOWN) && !event.repeat;
|
||||
bool released = (event.type == SDL_EVENT_KEY_UP);
|
||||
|
||||
if (pressed) {
|
||||
m_currentKeyState[scancode] = true;
|
||||
} else if (released) {
|
||||
m_currentKeyState[scancode] = false;
|
||||
}
|
||||
|
||||
// Notify handlers
|
||||
for (auto& handler : m_keyHandlers) {
|
||||
try {
|
||||
handler(scancode, pressed);
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Exception in key handler: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::handleMouseButtonEvent(const SDL_MouseButtonEvent& event) {
|
||||
int button = event.button;
|
||||
bool pressed = (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN);
|
||||
|
||||
m_currentMouseState[button] = pressed;
|
||||
|
||||
// Notify handlers
|
||||
for (auto& handler : m_mouseButtonHandlers) {
|
||||
try {
|
||||
handler(button, pressed, event.x, event.y);
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Exception in mouse button handler: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::handleMouseMotionEvent(const SDL_MouseMotionEvent& event) {
|
||||
float newX = event.x;
|
||||
float newY = event.y;
|
||||
|
||||
m_mouseDeltaX = newX - m_mouseX;
|
||||
m_mouseDeltaY = newY - m_mouseY;
|
||||
m_mouseX = newX;
|
||||
m_mouseY = newY;
|
||||
|
||||
// Notify handlers
|
||||
for (auto& handler : m_mouseMotionHandlers) {
|
||||
try {
|
||||
handler(m_mouseX, m_mouseY, m_mouseDeltaX, m_mouseDeltaY);
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Exception in mouse motion handler: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::handleWindowEvent(const SDL_WindowEvent& event) {
|
||||
// Notify handlers
|
||||
for (auto& handler : m_windowEventHandlers) {
|
||||
try {
|
||||
handler(event);
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Exception in window event handler: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::updateInputState() {
|
||||
// Copy current state to previous state
|
||||
m_previousKeyState = m_currentKeyState;
|
||||
m_previousMouseState = m_currentMouseState;
|
||||
}
|
||||
|
||||
void InputManager::updateDAS(float deltaTime) {
|
||||
if (!m_dasState.isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
float deltaMs = deltaTime * 1000.0f; // Convert to milliseconds
|
||||
|
||||
// Update delay timer
|
||||
if (m_dasState.delayTimer > 0.0f) {
|
||||
m_dasState.delayTimer -= deltaMs;
|
||||
}
|
||||
|
||||
// Update repeat timer (only if delay has expired)
|
||||
if (m_dasState.delayTimer <= 0.0f && m_dasState.repeatTimer > 0.0f) {
|
||||
m_dasState.repeatTimer -= deltaMs;
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::resetDAS() {
|
||||
m_dasState.isActive = false;
|
||||
m_dasState.delayTimer = 0.0f;
|
||||
m_dasState.repeatTimer = 0.0f;
|
||||
m_dasState.activeKey = SDL_SCANCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
// IInputHandler interface implementation
|
||||
bool InputManager::processEvent(const SDL_Event& event) {
|
||||
// Process individual event and return if it was handled
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
handleKeyEvent(event.key);
|
||||
return true;
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
handleMouseButtonEvent(event.button);
|
||||
return true;
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
handleMouseMotionEvent(event.motion);
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
handleWindowEvent(event.window);
|
||||
return true;
|
||||
case SDL_EVENT_QUIT:
|
||||
handleQuitEvent();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::update(double deltaTime) {
|
||||
update(static_cast<float>(deltaTime));
|
||||
}
|
||||
|
||||
bool InputManager::isKeyCurrentlyPressed(SDL_Scancode scancode) const {
|
||||
return isKeyHeld(scancode);
|
||||
}
|
||||
|
||||
bool InputManager::isKeyJustPressed(SDL_Scancode scancode) const {
|
||||
return isKeyPressed(scancode);
|
||||
}
|
||||
|
||||
bool InputManager::isKeyJustReleased(SDL_Scancode scancode) const {
|
||||
return isKeyReleased(scancode);
|
||||
}
|
||||
|
||||
bool InputManager::isQuitRequested() const {
|
||||
return shouldQuit();
|
||||
}
|
||||
|
||||
void InputManager::reset() {
|
||||
// Clear pressed/released states, keep held states
|
||||
// In the current InputManager implementation, we use previous/current state
|
||||
// so we just copy current to previous to reset the "just pressed/released" states
|
||||
m_previousKeyState = m_currentKeyState;
|
||||
m_previousMouseState = m_currentMouseState;
|
||||
}
|
||||
|
||||
void InputManager::handleQuitEvent() {
|
||||
m_shouldQuit = true;
|
||||
for (auto& handler : m_quitHandlers) {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user