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

@@ -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;