Phase 2: Core Systems: Input Management done
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
#include "ApplicationManager.h"
|
||||
#include "StateManager.h"
|
||||
#include "InputManager.h"
|
||||
#include "../graphics/RenderManager.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
@ -126,6 +127,13 @@ bool ApplicationManager::initializeManagers() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create InputManager
|
||||
m_inputManager = std::make_unique<InputManager>();
|
||||
if (!m_inputManager) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create InputManager");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create StateManager (will be enhanced in next steps)
|
||||
m_stateManager = std::make_unique<StateManager>(AppState::Loading);
|
||||
|
||||
@ -152,27 +160,28 @@ bool ApplicationManager::initializeGame() {
|
||||
}
|
||||
|
||||
void ApplicationManager::processEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
// Let InputManager process all SDL events
|
||||
if (m_inputManager) {
|
||||
m_inputManager->processEvents();
|
||||
|
||||
// Check if InputManager detected a quit request
|
||||
if (m_inputManager->shouldQuit()) {
|
||||
requestShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Route events through InputManager and StateManager
|
||||
// For now, handle basic window events
|
||||
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
||||
if (m_renderManager) {
|
||||
m_renderManager->handleWindowResize(event.window.data1, event.window.data2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any additional events not processed by InputManager
|
||||
// (In this case, we rely on InputManager for most event handling)
|
||||
}
|
||||
|
||||
void ApplicationManager::update(float deltaTime) {
|
||||
// TODO: Update all game systems
|
||||
// This will include StateManager updates, game logic, etc.
|
||||
|
||||
// Update InputManager
|
||||
if (m_inputManager) {
|
||||
m_inputManager->update(deltaTime);
|
||||
}
|
||||
|
||||
// Update StateManager
|
||||
if (m_stateManager) {
|
||||
m_stateManager->update(deltaTime);
|
||||
}
|
||||
@ -196,6 +205,7 @@ void ApplicationManager::render() {
|
||||
void ApplicationManager::cleanupManagers() {
|
||||
// Cleanup managers in reverse order
|
||||
m_stateManager.reset();
|
||||
m_inputManager.reset();
|
||||
m_renderManager.reset();
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Managers cleaned up");
|
||||
|
||||
@ -40,6 +40,7 @@ public:
|
||||
|
||||
// Access to managers (for now, will be replaced with dependency injection later)
|
||||
RenderManager* getRenderManager() const { return m_renderManager.get(); }
|
||||
InputManager* getInputManager() const { return m_inputManager.get(); }
|
||||
StateManager* getStateManager() const { return m_stateManager.get(); }
|
||||
|
||||
private:
|
||||
@ -59,6 +60,7 @@ private:
|
||||
|
||||
// Core managers
|
||||
std::unique_ptr<RenderManager> m_renderManager;
|
||||
std::unique_ptr<InputManager> m_inputManager;
|
||||
std::unique_ptr<StateManager> m_stateManager;
|
||||
|
||||
// Application state
|
||||
|
||||
290
src/core/InputManager.cpp
Normal file
290
src/core/InputManager.cpp
Normal file
@ -0,0 +1,290 @@
|
||||
#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)) {
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
m_shouldQuit = true;
|
||||
for (auto& handler : m_quitHandlers) {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
104
src/core/InputManager.h
Normal file
104
src/core/InputManager.h
Normal file
@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* InputManager - Centralized input handling system
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Process SDL events and maintain input state
|
||||
* - Provide clean interface for input queries
|
||||
* - Handle keyboard and mouse input
|
||||
* - Support event handler registration
|
||||
* - Implement game-specific input logic (DAS/ARR)
|
||||
*/
|
||||
class InputManager {
|
||||
public:
|
||||
// Event handler types
|
||||
using KeyHandler = std::function<void(SDL_Scancode key, bool pressed)>;
|
||||
using MouseButtonHandler = std::function<void(int button, bool pressed, float x, float y)>;
|
||||
using MouseMotionHandler = std::function<void(float x, float y, float deltaX, float deltaY)>;
|
||||
using WindowEventHandler = std::function<void(const SDL_WindowEvent& event)>;
|
||||
using QuitHandler = std::function<void()>;
|
||||
|
||||
InputManager();
|
||||
~InputManager() = default;
|
||||
|
||||
// Core input processing
|
||||
void processEvents();
|
||||
void update(float deltaTime);
|
||||
|
||||
// Keyboard state queries
|
||||
bool isKeyPressed(SDL_Scancode key) const; // True only on the frame key was pressed
|
||||
bool isKeyReleased(SDL_Scancode key) const; // True only on the frame key was released
|
||||
bool isKeyHeld(SDL_Scancode key) const; // True while key is down
|
||||
|
||||
// Mouse state queries
|
||||
bool isMouseButtonPressed(int button) const;
|
||||
bool isMouseButtonReleased(int button) const;
|
||||
bool isMouseButtonHeld(int button) const;
|
||||
void getMousePosition(float& x, float& y) const;
|
||||
void getMouseDelta(float& deltaX, float& deltaY) const;
|
||||
|
||||
// Event handler registration
|
||||
void registerKeyHandler(KeyHandler handler);
|
||||
void registerMouseButtonHandler(MouseButtonHandler handler);
|
||||
void registerMouseMotionHandler(MouseMotionHandler handler);
|
||||
void registerWindowEventHandler(WindowEventHandler handler);
|
||||
void registerQuitHandler(QuitHandler handler);
|
||||
|
||||
// Game-specific input utilities (DAS/ARR system for Tetris)
|
||||
struct DASState {
|
||||
bool isActive = false;
|
||||
float delayTimer = 0.0f;
|
||||
float repeatTimer = 0.0f;
|
||||
SDL_Scancode activeKey = SDL_SCANCODE_UNKNOWN;
|
||||
};
|
||||
|
||||
void configureDAS(float delayMs, float repeatMs);
|
||||
bool checkDASMovement(SDL_Scancode leftKey, SDL_Scancode rightKey, int& direction);
|
||||
|
||||
// Application control
|
||||
bool shouldQuit() const { return m_shouldQuit; }
|
||||
void requestQuit() { m_shouldQuit = true; }
|
||||
|
||||
private:
|
||||
// Input state tracking
|
||||
std::unordered_map<SDL_Scancode, bool> m_currentKeyState;
|
||||
std::unordered_map<SDL_Scancode, bool> m_previousKeyState;
|
||||
std::unordered_map<int, bool> m_currentMouseState;
|
||||
std::unordered_map<int, bool> m_previousMouseState;
|
||||
|
||||
// Mouse position tracking
|
||||
float m_mouseX = 0.0f;
|
||||
float m_mouseY = 0.0f;
|
||||
float m_mouseDeltaX = 0.0f;
|
||||
float m_mouseDeltaY = 0.0f;
|
||||
|
||||
// Event handlers
|
||||
std::vector<KeyHandler> m_keyHandlers;
|
||||
std::vector<MouseButtonHandler> m_mouseButtonHandlers;
|
||||
std::vector<MouseMotionHandler> m_mouseMotionHandlers;
|
||||
std::vector<WindowEventHandler> m_windowEventHandlers;
|
||||
std::vector<QuitHandler> m_quitHandlers;
|
||||
|
||||
// Application state
|
||||
bool m_shouldQuit = false;
|
||||
|
||||
// DAS/ARR system for Tetris-style movement
|
||||
DASState m_dasState;
|
||||
float m_dasDelay = 170.0f; // Default DAS delay in ms
|
||||
float m_dasRepeat = 40.0f; // Default ARR (Auto Repeat Rate) in ms
|
||||
|
||||
// Helper methods
|
||||
void handleKeyEvent(const SDL_KeyboardEvent& event);
|
||||
void handleMouseButtonEvent(const SDL_MouseButtonEvent& event);
|
||||
void handleMouseMotionEvent(const SDL_MouseMotionEvent& event);
|
||||
void handleWindowEvent(const SDL_WindowEvent& event);
|
||||
void updateInputState();
|
||||
void updateDAS(float deltaTime);
|
||||
void resetDAS();
|
||||
};
|
||||
Reference in New Issue
Block a user