feat: Enhance input handling and window services

- Added mouse delta tracking to InputState for improved mouse movement feedback.
- Introduced FPS mode toggle in GUI demo, allowing for relative mouse movement and cursor visibility control.
- Implemented input service methods for mouse position, delta, wheel, and key/action state checks.
- Updated SDL input service to handle relative mouse mode and mouse grabbing more effectively.
- Enhanced window service with methods to manage mouse grabbing and cursor visibility.
- Updated interface definitions for IInputService and IWindowService to include new functionalities.
This commit is contained in:
2026-01-05 14:49:40 +00:00
parent 13bfe28c25
commit 5fec5e9544
11 changed files with 688 additions and 37 deletions

View File

@@ -59,6 +59,8 @@ function InputState:new()
local instance = {
mouseX = 0,
mouseY = 0,
mouseDeltaX = 0,
mouseDeltaY = 0,
mouseDown = false,
mouseDownPrevious = false,
wheel = 0,
@@ -68,10 +70,12 @@ function InputState:new()
return setmetatable(instance, self)
end
function InputState:setMouse(x, y, isDown)
function InputState:setMouse(x, y, isDown, deltaX, deltaY)
self.mouseDownPrevious = self.mouseDown
self.mouseX = x
self.mouseY = y
self.mouseDeltaX = deltaX or 0
self.mouseDeltaY = deltaY or 0
self.mouseDown = isDown
end
@@ -102,6 +106,8 @@ end
function InputState:resetTransient()
self.textInput = ""
self.wheel = 0
self.mouseDeltaX = 0
self.mouseDeltaY = 0
end
local Context = {}

View File

@@ -20,6 +20,38 @@ local buttonStates = {
local statusMessage = 'Ready'
local viewProjectionLogged = false
local fpsMode = false
local fpsToggleWasDown = false
local function setFpsMode(enabled)
fpsMode = enabled
if window_set_relative_mouse_mode then
window_set_relative_mouse_mode(enabled)
end
if window_set_mouse_grabbed then
window_set_mouse_grabbed(enabled)
end
if window_set_cursor_visible then
window_set_cursor_visible(not enabled)
end
statusMessage = enabled and "FPS mode enabled" or "FPS mode disabled"
log_trace("FPS mode toggled: %s", enabled and "on" or "off")
end
local function updateFpsModeToggle()
if not input_is_key_down then
return
end
local down = input_is_key_down("F1")
if down and not fpsToggleWasDown then
setFpsMode(not fpsMode)
end
fpsToggleWasDown = down
if fpsMode and window_get_mouse_grabbed and not window_get_mouse_grabbed() then
fpsMode = false
statusMessage = "FPS mode disabled"
end
end
local shader_variants = {
default = {
@@ -83,14 +115,37 @@ local function drawTestButtons()
buttonStates.button2 and "ON" or "OFF",
buttonStates.button3 and "ON" or "OFF",
buttonStates.button4 and "ON" or "OFF")
Gui.text(ctx, {x = 70, y = 320, width = 360, height = 30}, statesText, {
Gui.text(ctx, {x = 70, y = 315, width = 360, height = 22}, statesText, {
fontSize = 16,
alignX = "center",
color = {1.0, 0.8, 0.8, 1.0},
})
local deltaX = input.mouseDeltaX or 0
local deltaY = input.mouseDeltaY or 0
Gui.text(ctx, {x = 70, y = 340, width = 360, height = 20},
string.format("Mouse d: %.1f, %.1f", deltaX, deltaY), {
fontSize = 14,
alignX = "center",
color = {0.85, 0.9, 0.85, 1.0},
})
local grabState = window_get_mouse_grabbed and window_get_mouse_grabbed() or false
local fpsLabel = fpsMode and "ON" or "OFF"
Gui.text(ctx, {x = 70, y = 362, width = 360, height = 20},
string.format("FPS Mode: %s (grab=%s, F1 toggle)", fpsLabel, grabState and "on" or "off"), {
fontSize = 14,
alignX = "center",
color = {0.85, 0.85, 0.95, 1.0},
})
if Gui.button(ctx, "fps_toggle", {x = 155, y = 385, width = 140, height = 28},
fpsMode and "FPS: ON" or "FPS: OFF") then
setFpsMode(not fpsMode)
end
-- Reset button
if Gui.button(ctx, "reset", {x = 175, y = 370, width = 100, height = 40}, "Reset") then
if Gui.button(ctx, "reset", {x = 175, y = 418, width = 100, height = 28}, "Reset") then
buttonStates.button1 = false
buttonStates.button2 = false
buttonStates.button3 = false
@@ -123,14 +178,31 @@ function get_view_projection(aspect)
end
function get_gui_commands()
updateFpsModeToggle()
ctx:beginFrame(input)
drawTestButtons()
Gui.cursor(ctx, input, {
size = 16,
thickness = 2,
color = {1.0, 0.9, 0.2, 1.0},
activeColor = {1.0, 0.35, 0.15, 1.0},
})
if fpsMode then
local width, height = 1024, 768
if window_get_size then
local w, h = window_get_size()
if type(w) == "number" and type(h) == "number" then
width, height = w, h
end
end
Gui.cursor(ctx, {mouseX = width * 0.5, mouseY = height * 0.5, mouseDown = input.mouseDown}, {
size = 18,
thickness = 2,
color = {0.9, 0.95, 1.0, 1.0},
activeColor = {1.0, 0.4, 0.2, 1.0},
})
else
Gui.cursor(ctx, input, {
size = 16,
thickness = 2,
color = {1.0, 0.9, 0.2, 1.0},
activeColor = {1.0, 0.35, 0.15, 1.0},
})
end
ctx:endFrame()
return ctx:getCommands()
end

View File

@@ -262,6 +262,8 @@ void ServiceBasedApp::RegisterServices() {
registry_.GetService<services::IMeshService>(),
registry_.GetService<services::IAudioCommandService>(),
registry_.GetService<services::IPhysicsBridgeService>(),
registry_.GetService<services::IInputService>(),
registry_.GetService<services::IWindowService>(),
runtimeConfig_.luaDebug);
// Script-facing services

View File

@@ -5,12 +5,64 @@
#include <btBulletDynamicsCommon.h>
#include <lua.hpp>
#include <SDL3/SDL.h>
#include <algorithm>
#include <array>
#include <cctype>
#include <stdexcept>
#include <string>
#include <utility>
namespace {
bool TryParseMouseButtonName(const char* name, uint8_t& button) {
if (!name) {
return false;
}
std::string lower(name);
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
if (lower == "left") {
button = SDL_BUTTON_LEFT;
return true;
}
if (lower == "right") {
button = SDL_BUTTON_RIGHT;
return true;
}
if (lower == "middle") {
button = SDL_BUTTON_MIDDLE;
return true;
}
if (lower == "x1" || lower == "extra1") {
button = SDL_BUTTON_X1;
return true;
}
if (lower == "x2" || lower == "extra2") {
button = SDL_BUTTON_X2;
return true;
}
return false;
}
SDL_Keycode ReadKeycodeFromLua(lua_State* L, int index, bool& ok) {
ok = true;
if (lua_isnumber(L, index)) {
return static_cast<SDL_Keycode>(lua_tointeger(L, index));
}
if (lua_isstring(L, index)) {
const char* name = lua_tostring(L, index);
SDL_Keycode key = SDL_GetKeyFromName(name);
if (key == SDLK_UNKNOWN) {
ok = false;
}
return key;
}
ok = false;
return SDLK_UNKNOWN;
}
} // namespace
namespace sdl3cpp::services::impl {
ScriptEngineService::ScriptEngineService(const std::filesystem::path& scriptPath,
@@ -18,11 +70,15 @@ ScriptEngineService::ScriptEngineService(const std::filesystem::path& scriptPath
std::shared_ptr<IMeshService> meshService,
std::shared_ptr<IAudioCommandService> audioCommandService,
std::shared_ptr<IPhysicsBridgeService> physicsBridgeService,
std::shared_ptr<IInputService> inputService,
std::shared_ptr<IWindowService> windowService,
bool debugEnabled)
: logger_(std::move(logger)),
meshService_(std::move(meshService)),
audioCommandService_(std::move(audioCommandService)),
physicsBridgeService_(std::move(physicsBridgeService)),
inputService_(std::move(inputService)),
windowService_(std::move(windowService)),
scriptPath_(scriptPath),
debugEnabled_(debugEnabled) {
if (logger_) {
@@ -31,7 +87,9 @@ ScriptEngineService::ScriptEngineService(const std::filesystem::path& scriptPath
", debugEnabled=" + std::string(debugEnabled_ ? "true" : "false") +
", meshService=" + std::string(meshService_ ? "set" : "null") +
", audioCommandService=" + std::string(audioCommandService_ ? "set" : "null") +
", physicsBridgeService=" + std::string(physicsBridgeService_ ? "set" : "null"));
", physicsBridgeService=" + std::string(physicsBridgeService_ ? "set" : "null") +
", inputService=" + std::string(inputService_ ? "set" : "null") +
", windowService=" + std::string(windowService_ ? "set" : "null"));
}
}
@@ -56,6 +114,8 @@ void ScriptEngineService::Initialize() {
bindingContext_->meshService = meshService_;
bindingContext_->audioCommandService = audioCommandService_;
bindingContext_->physicsBridgeService = physicsBridgeService_;
bindingContext_->inputService = inputService_;
bindingContext_->windowService = windowService_;
bindingContext_->logger = logger_;
luaState_ = luaL_newstate();
@@ -160,6 +220,22 @@ void ScriptEngineService::RegisterBindings(lua_State* L) {
bind("audio_play_background", &ScriptEngineService::AudioPlayBackground);
bind("audio_play_sound", &ScriptEngineService::AudioPlaySound);
bind("audio_stop_background", &ScriptEngineService::AudioStopBackground);
bind("input_get_mouse_position", &ScriptEngineService::InputGetMousePosition);
bind("input_get_mouse_delta", &ScriptEngineService::InputGetMouseDelta);
bind("input_get_mouse_wheel", &ScriptEngineService::InputGetMouseWheel);
bind("input_is_key_down", &ScriptEngineService::InputIsKeyDown);
bind("input_is_action_down", &ScriptEngineService::InputIsActionDown);
bind("input_is_mouse_down", &ScriptEngineService::InputIsMouseDown);
bind("input_get_text", &ScriptEngineService::InputGetText);
bind("window_get_size", &ScriptEngineService::WindowGetSize);
bind("window_set_title", &ScriptEngineService::WindowSetTitle);
bind("window_is_minimized", &ScriptEngineService::WindowIsMinimized);
bind("window_set_mouse_grabbed", &ScriptEngineService::WindowSetMouseGrabbed);
bind("window_get_mouse_grabbed", &ScriptEngineService::WindowGetMouseGrabbed);
bind("window_set_relative_mouse_mode", &ScriptEngineService::WindowSetRelativeMouseMode);
bind("window_get_relative_mouse_mode", &ScriptEngineService::WindowGetRelativeMouseMode);
bind("window_set_cursor_visible", &ScriptEngineService::WindowSetCursorVisible);
bind("window_is_cursor_visible", &ScriptEngineService::WindowIsCursorVisible);
}
int ScriptEngineService::LoadMeshFromFile(lua_State* L) {
@@ -384,6 +460,222 @@ int ScriptEngineService::AudioStopBackground(lua_State* L) {
return 1;
}
int ScriptEngineService::InputGetMousePosition(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->inputService) {
lua_pushnil(L);
lua_pushstring(L, "Input service not available");
return 2;
}
auto [x, y] = context->inputService->GetMousePosition();
lua_pushnumber(L, x);
lua_pushnumber(L, y);
return 2;
}
int ScriptEngineService::InputGetMouseDelta(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->inputService) {
lua_pushnil(L);
lua_pushstring(L, "Input service not available");
return 2;
}
const auto& state = context->inputService->GetState();
lua_pushnumber(L, state.mouseDeltaX);
lua_pushnumber(L, state.mouseDeltaY);
return 2;
}
int ScriptEngineService::InputGetMouseWheel(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->inputService) {
lua_pushnil(L);
lua_pushstring(L, "Input service not available");
return 2;
}
const auto& state = context->inputService->GetState();
lua_pushnumber(L, state.mouseWheelDeltaX);
lua_pushnumber(L, state.mouseWheelDeltaY);
return 2;
}
int ScriptEngineService::InputIsKeyDown(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->inputService) {
lua_pushnil(L);
lua_pushstring(L, "Input service not available");
return 2;
}
bool ok = false;
SDL_Keycode key = ReadKeycodeFromLua(L, 1, ok);
if (!ok) {
lua_pushboolean(L, 0);
return 1;
}
lua_pushboolean(L, context->inputService->IsKeyPressed(key));
return 1;
}
int ScriptEngineService::InputIsActionDown(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->inputService) {
lua_pushnil(L);
lua_pushstring(L, "Input service not available");
return 2;
}
const char* action = luaL_checkstring(L, 1);
lua_pushboolean(L, context->inputService->IsActionPressed(action));
return 1;
}
int ScriptEngineService::InputIsMouseDown(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->inputService) {
lua_pushnil(L);
lua_pushstring(L, "Input service not available");
return 2;
}
uint8_t button = SDL_BUTTON_LEFT;
if (lua_isnumber(L, 1)) {
button = static_cast<uint8_t>(lua_tointeger(L, 1));
} else if (lua_isstring(L, 1)) {
const char* name = lua_tostring(L, 1);
if (!TryParseMouseButtonName(name, button)) {
lua_pushboolean(L, 0);
return 1;
}
} else {
lua_pushboolean(L, 0);
return 1;
}
lua_pushboolean(L, context->inputService->IsMouseButtonPressed(button));
return 1;
}
int ScriptEngineService::InputGetText(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->inputService) {
lua_pushnil(L);
lua_pushstring(L, "Input service not available");
return 2;
}
const auto& state = context->inputService->GetState();
lua_pushstring(L, state.textInput.c_str());
return 1;
}
int ScriptEngineService::WindowGetSize(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
auto [width, height] = context->windowService->GetSize();
lua_pushinteger(L, static_cast<lua_Integer>(width));
lua_pushinteger(L, static_cast<lua_Integer>(height));
return 2;
}
int ScriptEngineService::WindowSetTitle(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
const char* title = luaL_checkstring(L, 1);
context->windowService->SetTitle(title);
lua_pushboolean(L, 1);
return 1;
}
int ScriptEngineService::WindowIsMinimized(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
lua_pushboolean(L, context->windowService->IsMinimized());
return 1;
}
int ScriptEngineService::WindowSetMouseGrabbed(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
bool grabbed = lua_toboolean(L, 1);
context->windowService->SetMouseGrabbed(grabbed);
lua_pushboolean(L, 1);
return 1;
}
int ScriptEngineService::WindowGetMouseGrabbed(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
lua_pushboolean(L, context->windowService->IsMouseGrabbed());
return 1;
}
int ScriptEngineService::WindowSetRelativeMouseMode(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
bool enabled = lua_toboolean(L, 1);
context->windowService->SetRelativeMouseMode(enabled);
if (context->inputService) {
context->inputService->SetRelativeMouseMode(enabled);
}
lua_pushboolean(L, 1);
return 1;
}
int ScriptEngineService::WindowGetRelativeMouseMode(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
lua_pushboolean(L, context->windowService->IsRelativeMouseMode());
return 1;
}
int ScriptEngineService::WindowSetCursorVisible(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
bool visible = lua_toboolean(L, 1);
context->windowService->SetCursorVisible(visible);
lua_pushboolean(L, 1);
return 1;
}
int ScriptEngineService::WindowIsCursorVisible(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!context || !context->windowService) {
lua_pushnil(L);
lua_pushstring(L, "Window service not available");
return 2;
}
lua_pushboolean(L, context->windowService->IsCursorVisible());
return 1;
}
int ScriptEngineService::GlmMatrixFromTransform(lua_State* L) {
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
auto logger = context ? context->logger : nullptr;

View File

@@ -4,6 +4,8 @@
#include "../interfaces/i_audio_command_service.hpp"
#include "../interfaces/i_mesh_service.hpp"
#include "../interfaces/i_physics_bridge_service.hpp"
#include "../interfaces/i_input_service.hpp"
#include "../interfaces/i_window_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "../../di/lifecycle.hpp"
#include <filesystem>
@@ -25,6 +27,8 @@ public:
std::shared_ptr<IMeshService> meshService,
std::shared_ptr<IAudioCommandService> audioCommandService,
std::shared_ptr<IPhysicsBridgeService> physicsBridgeService,
std::shared_ptr<IInputService> inputService,
std::shared_ptr<IWindowService> windowService,
bool debugEnabled = false);
~ScriptEngineService() override;
@@ -47,6 +51,8 @@ private:
std::shared_ptr<IMeshService> meshService;
std::shared_ptr<IAudioCommandService> audioCommandService;
std::shared_ptr<IPhysicsBridgeService> physicsBridgeService;
std::shared_ptr<IInputService> inputService;
std::shared_ptr<IWindowService> windowService;
std::shared_ptr<ILogger> logger;
};
@@ -59,11 +65,29 @@ private:
static int AudioPlaySound(lua_State* L);
static int AudioStopBackground(lua_State* L);
static int GlmMatrixFromTransform(lua_State* L);
static int InputGetMousePosition(lua_State* L);
static int InputGetMouseDelta(lua_State* L);
static int InputGetMouseWheel(lua_State* L);
static int InputIsKeyDown(lua_State* L);
static int InputIsActionDown(lua_State* L);
static int InputIsMouseDown(lua_State* L);
static int InputGetText(lua_State* L);
static int WindowGetSize(lua_State* L);
static int WindowSetTitle(lua_State* L);
static int WindowIsMinimized(lua_State* L);
static int WindowSetMouseGrabbed(lua_State* L);
static int WindowGetMouseGrabbed(lua_State* L);
static int WindowSetRelativeMouseMode(lua_State* L);
static int WindowGetRelativeMouseMode(lua_State* L);
static int WindowSetCursorVisible(lua_State* L);
static int WindowIsCursorVisible(lua_State* L);
std::shared_ptr<ILogger> logger_;
std::shared_ptr<IMeshService> meshService_;
std::shared_ptr<IAudioCommandService> audioCommandService_;
std::shared_ptr<IPhysicsBridgeService> physicsBridgeService_;
std::shared_ptr<IInputService> inputService_;
std::shared_ptr<IWindowService> windowService_;
std::filesystem::path scriptPath_;
std::filesystem::path scriptDirectory_;
bool debugEnabled_ = false;

View File

@@ -66,6 +66,10 @@ SdlInputService::SdlInputService(std::shared_ptr<events::IEventBus> eventBus,
OnTextInput(e);
});
eventBus_->Subscribe(events::EventType::WindowResized, [this](const events::Event& e) {
OnWindowResized(e);
});
eventBus_->Subscribe(events::EventType::WindowFocusGained, [this](const events::Event& e) {
OnWindowFocusGained(e);
});
@@ -86,9 +90,23 @@ SdlInputService::SdlInputService(std::shared_ptr<events::IEventBus> eventBus,
const auto& mouseGrabConfig = configService_->GetMouseGrabConfig();
mouseGrabGatesLook_ = mouseGrabConfig.enabled &&
(mouseGrabConfig.grabOnClick || mouseGrabConfig.startGrabbed);
mouseRelativeMode_ = mouseGrabConfig.relativeMode;
guiWindowWidth_ = configService_->GetWindowWidth();
guiWindowHeight_ = configService_->GetWindowHeight();
if (guiWindowWidth_ > 0 && guiWindowHeight_ > 0) {
guiCursorX_ = static_cast<float>(guiWindowWidth_) * 0.5f;
guiCursorY_ = static_cast<float>(guiWindowHeight_) * 0.5f;
state_.mouseX = guiCursorX_;
state_.mouseY = guiCursorY_;
guiInputSnapshot_.mouseX = guiCursorX_;
guiInputSnapshot_.mouseY = guiCursorY_;
}
if (logger_) {
logger_->Trace("SdlInputService", "SdlInputService",
"mouseGrabGatesLook=" + std::string(mouseGrabGatesLook_ ? "true" : "false"));
"mouseGrabGatesLook=" + std::string(mouseGrabGatesLook_ ? "true" : "false") +
", relativeMode=" + std::string(mouseRelativeMode_ ? "true" : "false") +
", guiWindow=" + std::to_string(guiWindowWidth_) + "x" +
std::to_string(guiWindowHeight_));
}
}
BuildActionKeyMapping();
@@ -118,15 +136,10 @@ void SdlInputService::ProcessEvent(const SDL_Event& event) {
break;
case SDL_EVENT_MOUSE_MOTION:
state_.mouseX = event.motion.x;
state_.mouseY = event.motion.y;
// GUI input processing
guiInputSnapshot_.mouseX = static_cast<float>(event.motion.x);
guiInputSnapshot_.mouseY = static_cast<float>(event.motion.y);
if (ShouldCaptureMouseDelta()) {
guiInputSnapshot_.mouseDeltaX += static_cast<float>(event.motion.xrel);
guiInputSnapshot_.mouseDeltaY += static_cast<float>(event.motion.yrel);
}
UpdateMousePosition(static_cast<float>(event.motion.x),
static_cast<float>(event.motion.y),
static_cast<float>(event.motion.xrel),
static_cast<float>(event.motion.yrel));
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
@@ -168,6 +181,8 @@ void SdlInputService::ResetFrameState() {
logger_->Trace("SdlInputService", "ResetFrameState");
}
// Reset per-frame state
state_.mouseDeltaX = 0.0f;
state_.mouseDeltaY = 0.0f;
state_.mouseWheelDeltaX = 0.0f;
state_.mouseWheelDeltaY = 0.0f;
state_.textInput.clear();
@@ -195,6 +210,14 @@ bool SdlInputService::IsMouseButtonPressed(uint8_t button) const {
return state_.mouseButtonsPressed.count(button) > 0;
}
bool SdlInputService::IsActionPressed(const std::string& action) const {
if (logger_) {
logger_->Trace("SdlInputService", "IsActionPressed",
"action=" + action);
}
return IsActionKeyPressed(action);
}
std::pair<float, float> SdlInputService::GetMousePosition() const {
if (logger_) {
logger_->Trace("SdlInputService", "GetMousePosition");
@@ -202,6 +225,21 @@ std::pair<float, float> SdlInputService::GetMousePosition() const {
return {state_.mouseX, state_.mouseY};
}
void SdlInputService::SetRelativeMouseMode(bool enabled) {
if (logger_) {
logger_->Trace("SdlInputService", "SetRelativeMouseMode",
"enabled=" + std::string(enabled ? "true" : "false"));
}
mouseRelativeMode_ = enabled;
}
bool SdlInputService::IsRelativeMouseMode() const {
if (logger_) {
logger_->Trace("SdlInputService", "IsRelativeMouseMode");
}
return mouseRelativeMode_;
}
void SdlInputService::OnKeyPressed(const events::Event& event) {
const auto& keyEvent = event.GetData<events::KeyEvent>();
if (logger_) {
@@ -237,14 +275,7 @@ void SdlInputService::OnMouseMoved(const events::Event& event) {
", deltaX=" + std::to_string(mouseEvent.deltaX) +
", deltaY=" + std::to_string(mouseEvent.deltaY));
}
state_.mouseX = mouseEvent.x;
state_.mouseY = mouseEvent.y;
guiInputSnapshot_.mouseX = mouseEvent.x;
guiInputSnapshot_.mouseY = mouseEvent.y;
if (ShouldCaptureMouseDelta()) {
guiInputSnapshot_.mouseDeltaX += mouseEvent.deltaX;
guiInputSnapshot_.mouseDeltaY += mouseEvent.deltaY;
}
UpdateMousePosition(mouseEvent.x, mouseEvent.y, mouseEvent.deltaX, mouseEvent.deltaY);
}
void SdlInputService::OnMouseButtonPressed(const events::Event& event) {
@@ -300,6 +331,18 @@ void SdlInputService::OnTextInput(const events::Event& event) {
guiInputSnapshot_.textInput += textEvent.text;
}
void SdlInputService::OnWindowResized(const events::Event& event) {
const auto& resized = event.GetData<events::WindowResizedEvent>();
guiWindowWidth_ = resized.width;
guiWindowHeight_ = resized.height;
ClampGuiCursor();
if (logger_) {
logger_->Trace("SdlInputService", "OnWindowResized",
"width=" + std::to_string(guiWindowWidth_) +
", height=" + std::to_string(guiWindowHeight_));
}
}
void SdlInputService::OnWindowFocusGained(const events::Event& event) {
(void)event;
windowFocused_ = true;
@@ -530,6 +573,46 @@ bool SdlInputService::ShouldCaptureMouseDelta() const {
return true;
}
void SdlInputService::UpdateMousePosition(float x, float y, float deltaX, float deltaY) {
if (mouseRelativeMode_ && mouseGrabbed_) {
guiCursorX_ += deltaX;
guiCursorY_ += deltaY;
ClampGuiCursor();
state_.mouseX = guiCursorX_;
state_.mouseY = guiCursorY_;
guiInputSnapshot_.mouseX = guiCursorX_;
guiInputSnapshot_.mouseY = guiCursorY_;
if (logger_ && !relativeCursorLogged_) {
logger_->Trace("SdlInputService", "UpdateMousePosition",
"relativeCursor=true, startX=" + std::to_string(guiCursorX_) +
", startY=" + std::to_string(guiCursorY_));
relativeCursorLogged_ = true;
}
} else {
state_.mouseX = x;
state_.mouseY = y;
guiInputSnapshot_.mouseX = x;
guiInputSnapshot_.mouseY = y;
guiCursorX_ = x;
guiCursorY_ = y;
}
if (ShouldCaptureMouseDelta()) {
guiInputSnapshot_.mouseDeltaX += deltaX;
guiInputSnapshot_.mouseDeltaY += deltaY;
}
state_.mouseDeltaX += deltaX;
state_.mouseDeltaY += deltaY;
}
void SdlInputService::ClampGuiCursor() {
if (guiWindowWidth_ == 0 || guiWindowHeight_ == 0) {
return;
}
guiCursorX_ = std::clamp(guiCursorX_, 0.0f, static_cast<float>(guiWindowWidth_ - 1));
guiCursorY_ = std::clamp(guiCursorY_, 0.0f, static_cast<float>(guiWindowHeight_ - 1));
}
void SdlInputService::EnsureGamepadSubsystem() {
uint32_t initialized = SDL_WasInit(0);
if ((initialized & SDL_INIT_GAMEPAD) != 0) {

View File

@@ -41,7 +41,10 @@ public:
}
bool IsKeyPressed(SDL_Keycode key) const override;
bool IsMouseButtonPressed(uint8_t button) const override;
bool IsActionPressed(const std::string& action) const override;
std::pair<float, float> GetMousePosition() const override;
void SetRelativeMouseMode(bool enabled) override;
bool IsRelativeMouseMode() const override;
void SetGuiScriptService(IGuiScriptService* guiScriptService) override;
void UpdateGuiInput() override;
@@ -56,6 +59,12 @@ private:
bool windowFocused_ = true;
bool mouseGrabbed_ = false;
bool mouseGrabGatesLook_ = false;
bool mouseRelativeMode_ = false;
float guiCursorX_ = 0.0f;
float guiCursorY_ = 0.0f;
uint32_t guiWindowWidth_ = 0;
uint32_t guiWindowHeight_ = 0;
bool relativeCursorLogged_ = false;
SDL_GamepadButton musicToggleButton_ = SDL_GAMEPAD_BUTTON_START;
SDL_GamepadButton dpadUpButton_ = SDL_GAMEPAD_BUTTON_DPAD_UP;
SDL_GamepadButton dpadDownButton_ = SDL_GAMEPAD_BUTTON_DPAD_DOWN;
@@ -78,6 +87,7 @@ private:
void OnMouseButtonReleased(const events::Event& event);
void OnMouseWheel(const events::Event& event);
void OnTextInput(const events::Event& event);
void OnWindowResized(const events::Event& event);
void OnWindowFocusGained(const events::Event& event);
void OnWindowFocusLost(const events::Event& event);
void OnMouseGrabChanged(const events::Event& event);
@@ -89,6 +99,8 @@ private:
void ApplyKeyMapping(SDL_Keycode key, bool isDown);
bool IsActionKeyPressed(const std::string& action) const;
bool ShouldCaptureMouseDelta() const;
void UpdateMousePosition(float x, float y, float deltaX, float deltaY);
void ClampGuiCursor();
// GUI key mapping (extracted from old Sdl3App)
static const std::unordered_map<SDL_Keycode, std::string> kGuiKeyNames;

View File

@@ -215,7 +215,7 @@ void SdlWindowService::CreateWindow(const WindowConfig& config) {
mouseGrabbed_ = false;
ConfigureMouseGrabBindings();
if (mouseGrabConfig_.enabled && mouseGrabConfig_.startGrabbed) {
ApplyMouseGrab(true);
ApplyMouseGrab(true, false);
}
logger_->TraceVariable("window_", reinterpret_cast<void*>(window_));
@@ -228,7 +228,7 @@ void SdlWindowService::DestroyWindow() {
"windowIsNull=" + std::string(window_ ? "false" : "true"));
if (window_) {
if (mouseGrabbed_) {
ApplyMouseGrab(false);
ApplyMouseGrab(false, false);
}
SDL_StopTextInput(window_);
SDL_DestroyWindow(window_);
@@ -259,6 +259,81 @@ bool SdlWindowService::IsMinimized() const {
return (flags & SDL_WINDOW_MINIMIZED) != 0;
}
void SdlWindowService::SetMouseGrabbed(bool grabbed) {
if (logger_) {
logger_->Trace("SdlWindowService", "SetMouseGrabbed",
"grabbed=" + std::string(grabbed ? "true" : "false"));
}
if (!window_) {
return;
}
if (!mouseGrabConfig_.enabled) {
mouseGrabConfig_.enabled = true;
}
ApplyMouseGrab(grabbed, true);
}
bool SdlWindowService::IsMouseGrabbed() const {
if (logger_) {
logger_->Trace("SdlWindowService", "IsMouseGrabbed");
}
return mouseGrabbed_;
}
void SdlWindowService::SetRelativeMouseMode(bool enabled) {
if (logger_) {
logger_->Trace("SdlWindowService", "SetRelativeMouseMode",
"enabled=" + std::string(enabled ? "true" : "false"));
}
mouseGrabConfig_.relativeMode = enabled;
if (!window_) {
return;
}
if (!SDL_SetWindowRelativeMouseMode(window_, enabled) && logger_) {
logger_->Error("SdlWindowService: " +
BuildSdlErrorMessage("SDL_SetWindowRelativeMouseMode failed", platformService_));
}
if (mouseGrabbed_ && mouseGrabConfig_.enabled) {
ApplyMouseGrab(mouseGrabbed_, true);
}
}
bool SdlWindowService::IsRelativeMouseMode() const {
if (logger_) {
logger_->Trace("SdlWindowService", "IsRelativeMouseMode");
}
return mouseGrabConfig_.relativeMode;
}
void SdlWindowService::SetCursorVisible(bool visible) {
if (logger_) {
logger_->Trace("SdlWindowService", "SetCursorVisible",
"visible=" + std::string(visible ? "true" : "false"));
}
if (!window_) {
return;
}
mouseGrabConfig_.hideCursor = !visible;
bool cursorResult = visible ? SDL_ShowCursor() : SDL_HideCursor();
if (!cursorResult && logger_) {
logger_->Error("SdlWindowService: " +
BuildSdlErrorMessage(visible ? "SDL_ShowCursor failed" : "SDL_HideCursor failed",
platformService_));
} else {
cursorVisible_ = visible;
}
if (mouseGrabbed_ && mouseGrabConfig_.enabled) {
ApplyMouseGrab(mouseGrabbed_, true);
}
}
bool SdlWindowService::IsCursorVisible() const {
if (logger_) {
logger_->Trace("SdlWindowService", "IsCursorVisible");
}
return cursorVisible_;
}
void SdlWindowService::PollEvents() {
logger_->Trace("SdlWindowService", "PollEvents");
SDL_Event event;
@@ -273,7 +348,7 @@ void SdlWindowService::PollEvents() {
logger_->Info("SdlWindowService: Mouse grab triggered by click (button=" +
std::to_string(event.button.button) + ")");
}
ApplyMouseGrab(true);
ApplyMouseGrab(true, false);
}
// Handle release separately (doesn't suppress event)
@@ -284,7 +359,7 @@ void SdlWindowService::PollEvents() {
if (logger_) {
logger_->Info("SdlWindowService: Mouse grab released by escape key");
}
ApplyMouseGrab(false);
ApplyMouseGrab(false, false);
}
// Only publish event if it's not the grab-triggering click
@@ -348,7 +423,7 @@ void SdlWindowService::HandleMouseGrabEvent(const SDL_Event& sdlEvent) {
if (mouseGrabConfig_.grabOnClick &&
sdlEvent.type == SDL_EVENT_MOUSE_BUTTON_DOWN &&
sdlEvent.button.button == grabMouseButton_) {
ApplyMouseGrab(true);
ApplyMouseGrab(true, false);
return;
}
@@ -356,15 +431,15 @@ void SdlWindowService::HandleMouseGrabEvent(const SDL_Event& sdlEvent) {
sdlEvent.type == SDL_EVENT_KEY_DOWN &&
sdlEvent.key.key == releaseKey_ &&
!sdlEvent.key.repeat) {
ApplyMouseGrab(false);
ApplyMouseGrab(false, false);
}
}
void SdlWindowService::ApplyMouseGrab(bool grabbed) {
void SdlWindowService::ApplyMouseGrab(bool grabbed, bool force) {
if (!window_ || !mouseGrabConfig_.enabled) {
return;
}
if (mouseGrabbed_ == grabbed) {
if (!force && mouseGrabbed_ == grabbed) {
return;
}
@@ -400,6 +475,16 @@ void SdlWindowService::ApplyMouseGrab(bool grabbed) {
logger_->Error("SdlWindowService: " +
BuildSdlErrorMessage(grabbed ? "SDL_HideCursor failed" : "SDL_ShowCursor failed",
platformService_));
} else {
cursorVisible_ = !grabbed;
}
} else {
bool cursorResult = SDL_ShowCursor();
if (!cursorResult && logger_) {
logger_->Error("SdlWindowService: " +
BuildSdlErrorMessage("SDL_ShowCursor failed", platformService_));
} else {
cursorVisible_ = true;
}
}

View File

@@ -53,6 +53,12 @@ public:
void PollEvents() override;
void SetTitle(const std::string& title) override;
bool IsMinimized() const override;
void SetMouseGrabbed(bool grabbed) override;
bool IsMouseGrabbed() const override;
void SetRelativeMouseMode(bool enabled) override;
bool IsRelativeMouseMode() const override;
void SetCursorVisible(bool visible) override;
bool IsCursorVisible() const override;
private:
std::shared_ptr<ILogger> logger_;
@@ -63,6 +69,7 @@ private:
bool initialized_ = false;
MouseGrabConfig mouseGrabConfig_{};
bool mouseGrabbed_ = false;
bool cursorVisible_ = true;
uint8_t grabMouseButton_ = SDL_BUTTON_LEFT;
SDL_Keycode releaseKey_ = SDLK_ESCAPE;
@@ -70,7 +77,7 @@ private:
void PublishEvent(const SDL_Event& sdlEvent);
double GetCurrentTime() const;
void HandleMouseGrabEvent(const SDL_Event& sdlEvent);
void ApplyMouseGrab(bool grabbed);
void ApplyMouseGrab(bool grabbed, bool force);
void ConfigureMouseGrabBindings();
};

View File

@@ -15,6 +15,8 @@ class IGuiScriptService;
struct InputState {
float mouseX = 0.0f;
float mouseY = 0.0f;
float mouseDeltaX = 0.0f;
float mouseDeltaY = 0.0f;
float mouseWheelDeltaX = 0.0f;
float mouseWheelDeltaY = 0.0f;
std::unordered_set<SDL_Keycode> keysPressed;
@@ -74,6 +76,14 @@ public:
*/
virtual bool IsMouseButtonPressed(uint8_t button) const = 0;
/**
* @brief Check if an action is currently pressed based on input bindings.
*
* @param action The action name to check
* @return true if the action is pressed, false otherwise
*/
virtual bool IsActionPressed(const std::string& action) const = 0;
/**
* @brief Get the current mouse position.
*
@@ -81,6 +91,22 @@ public:
*/
virtual std::pair<float, float> GetMousePosition() const = 0;
/**
* @brief Set whether mouse input should be treated as relative motion.
*
* This updates internal cursor tracking for script usage.
*
* @param enabled true for relative mode, false for absolute
*/
virtual void SetRelativeMouseMode(bool enabled) = 0;
/**
* @brief Check whether mouse input is treated as relative motion.
*
* @return true if relative mode is enabled, false otherwise
*/
virtual bool IsRelativeMouseMode() const = 0;
/**
* @brief Set the GUI script service for GUI input processing.
*

View File

@@ -92,6 +92,48 @@ public:
* @return true if minimized, false otherwise
*/
virtual bool IsMinimized() const = 0;
/**
* @brief Set mouse grab (capture) state.
*
* @param grabbed true to grab, false to release
*/
virtual void SetMouseGrabbed(bool grabbed) = 0;
/**
* @brief Check if the mouse is currently grabbed.
*
* @return true if grabbed, false otherwise
*/
virtual bool IsMouseGrabbed() const = 0;
/**
* @brief Enable or disable relative mouse mode.
*
* @param enabled true for relative mode, false for absolute
*/
virtual void SetRelativeMouseMode(bool enabled) = 0;
/**
* @brief Check if relative mouse mode is enabled.
*
* @return true if enabled, false otherwise
*/
virtual bool IsRelativeMouseMode() const = 0;
/**
* @brief Show or hide the OS cursor.
*
* @param visible true to show, false to hide
*/
virtual void SetCursorVisible(bool visible) = 0;
/**
* @brief Check if the OS cursor is visible.
*
* @return true if visible, false otherwise
*/
virtual bool IsCursorVisible() const = 0;
};
} // namespace sdl3cpp::services