From d41025b91b2ea11e9599d2f39ce91b519fa6573e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 14 Sep 2026 00:12:46 -0300 Subject: [PATCH] Fix multi-window input routing regressions Add a backend-neutral Input::pushEvent() API with SDL2 and SDL3 implementations, allowing native input translation and routing to be covered without requiring physical input or backend-specific test code. Add regression coverage ensuring a native mouse-wheel event is emitted exactly once and delivered only to its target application window. Resolve event coordinates and resize dimensions using the target window's scale instead of the window polling SDL's global event queue. This preserves correct input and sizing across windows on displays with different DPI scales. Also restore SDL3 compatibility with versions older than 3.2.12 when deriving integer wheel directions from floating-point offsets. --- include/eepp/window/input.hpp | 4 ++ src/eepp/window/backend/SDL2/inputsdl2.cpp | 46 ++++++++++++--- src/eepp/window/backend/SDL2/inputsdl2.hpp | 2 + src/eepp/window/backend/SDL3/inputsdl3.cpp | 66 +++++++++++++++++----- src/eepp/window/backend/SDL3/inputsdl3.hpp | 2 + src/eepp/window/input.cpp | 6 ++ src/tests/unit_tests/uiscenenode_tests.cpp | 50 ++++++++++++++++ 7 files changed, 154 insertions(+), 22 deletions(-) diff --git a/include/eepp/window/input.hpp b/include/eepp/window/input.hpp index a2799745d..48aa8a1ab 100644 --- a/include/eepp/window/input.hpp +++ b/include/eepp/window/input.hpp @@ -233,6 +233,10 @@ class EE_API Input { /** Send an input event to the window */ void sendEvent( InputEvent* Event ); + /** Injects an input event through the backend event translation path and routes it according to + * InputEvent::WinID. */ + virtual bool pushEvent( const InputEvent& event ); + /** @return The joystick manager */ JoystickManager* getJoystickManager() const; diff --git a/src/eepp/window/backend/SDL2/inputsdl2.cpp b/src/eepp/window/backend/SDL2/inputsdl2.cpp index b71354ec7..c16115528 100644 --- a/src/eepp/window/backend/SDL2/inputsdl2.cpp +++ b/src/eepp/window/backend/SDL2/inputsdl2.cpp @@ -11,6 +11,14 @@ namespace EE { namespace Window { namespace Backend { namespace SDL2 { +static Float getEventWindowScale( EE::Window::Window* pollingWindow, Uint32 windowId ) { + if ( windowId == 0 || windowId == pollingWindow->getWindowID() ) + return pollingWindow->getScale(); + if ( auto* eventWindow = Engine::instance()->getWindowID( windowId ) ) + return eventWindow->getScale(); + return pollingWindow->getScale(); +} + InputSDL::InputSDL( EE::Window::Window* window ) : Input( window, eeNew( JoystickManagerSDL, () ) ), mDPIScale( 1.f ) { #if defined( EE_X11_PLATFORM ) @@ -86,6 +94,26 @@ bool InputSDL::isMouseCaptured() const { SDL_WINDOW_MOUSE_CAPTURE; } +bool InputSDL::pushEvent( const InputEvent& event ) { + if ( event.Type != InputEvent::MouseWheel ) + return Input::pushEvent( event ); + + SDL_Event sdlEvent{}; + sdlEvent.type = SDL_MOUSEWHEEL; + sdlEvent.wheel.windowID = event.WinID; + sdlEvent.wheel.x = static_cast( event.wheel.x ); + sdlEvent.wheel.y = static_cast( event.wheel.y ); + sdlEvent.wheel.direction = event.wheel.direction == InputEvent::WheelEvent::Normal + ? SDL_MOUSEWHEEL_NORMAL + : SDL_MOUSEWHEEL_FLIPPED; +#if SDL_VERSION_ATLEAST( 2, 0, 18 ) + sdlEvent.wheel.preciseX = event.wheel.x; + sdlEvent.wheel.preciseY = event.wheel.y; +#endif + sendEvent( sdlEvent ); + return true; +} + std::string InputSDL::getKeyName( const Keycode& keyCode ) const { return std::string( SDL_GetKeyName( keyCode ) ); } @@ -123,9 +151,12 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) { case SDL_WINDOWEVENT_RESIZED: { event.Type = InputEvent::VideoResize; event.WinID = SDLEvent.window.windowID; - mDPIScale = mWindow->getScale(); - event.resize.w = SDLEvent.window.data1 * mDPIScale; - event.resize.h = SDLEvent.window.data2 * mDPIScale; + const Float eventWindowScale = + getEventWindowScale( mWindow, SDLEvent.window.windowID ); + if ( SDLEvent.window.windowID == mWindow->getWindowID() ) + mDPIScale = eventWindowScale; + event.resize.w = SDLEvent.window.data1 * eventWindowScale; + event.resize.h = SDLEvent.window.data2 * eventWindowScale; break; } case SDL_WINDOWEVENT_HIT_TEST: { @@ -291,13 +322,14 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) { break; } case SDL_MOUSEMOTION: { + const Float eventWindowScale = getEventWindowScale( mWindow, SDLEvent.motion.windowID ); event.Type = InputEvent::MouseMotion; event.motion.which = SDLEvent.motion.windowID; event.motion.state = SDLEvent.motion.state; - event.motion.x = SDLEvent.motion.x * mDPIScale; - event.motion.y = SDLEvent.motion.y * mDPIScale; - event.motion.xrel = SDLEvent.motion.xrel * mDPIScale; - event.motion.yrel = SDLEvent.motion.yrel * mDPIScale; + event.motion.x = SDLEvent.motion.x * eventWindowScale; + event.motion.y = SDLEvent.motion.y * eventWindowScale; + event.motion.xrel = SDLEvent.motion.xrel * eventWindowScale; + event.motion.yrel = SDLEvent.motion.yrel * eventWindowScale; event.WinID = SDLEvent.motion.windowID; break; } diff --git a/src/eepp/window/backend/SDL2/inputsdl2.hpp b/src/eepp/window/backend/SDL2/inputsdl2.hpp index d786ffb1d..8b37a6113 100644 --- a/src/eepp/window/backend/SDL2/inputsdl2.hpp +++ b/src/eepp/window/backend/SDL2/inputsdl2.hpp @@ -30,6 +30,8 @@ class EE_API InputSDL : public Input { bool isMouseCaptured() const; + bool pushEvent( const InputEvent& event ); + std::string getKeyName( const Keycode& keycode ) const; Keycode getKeyFromName( const std::string& keycode ) const; diff --git a/src/eepp/window/backend/SDL3/inputsdl3.cpp b/src/eepp/window/backend/SDL3/inputsdl3.cpp index aad28916f..50c67dfb5 100644 --- a/src/eepp/window/backend/SDL3/inputsdl3.cpp +++ b/src/eepp/window/backend/SDL3/inputsdl3.cpp @@ -7,6 +7,14 @@ namespace EE { namespace Window { namespace Backend { namespace SDL3 { +static Float getEventWindowScale( EE::Window::Window* pollingWindow, Uint32 windowId ) { + if ( windowId == 0 || windowId == pollingWindow->getWindowID() ) + return pollingWindow->getScale(); + if ( auto* eventWindow = Engine::instance()->getWindowID( windowId ) ) + return eventWindow->getScale(); + return pollingWindow->getScale(); +} + InputSDL::InputSDL( Window* window ) : Input( window, eeNew( JoystickManagerSDL, () ) ), mDPIScale( 1.f ) { #if defined( EE_X11_PLATFORM ) @@ -77,6 +85,26 @@ bool InputSDL::isMouseCaptured() const { return SDL_GetWindowFlags( win ) & SDL_WINDOW_MOUSE_CAPTURE; } +bool InputSDL::pushEvent( const InputEvent& event ) { + if ( event.Type != InputEvent::MouseWheel ) + return Input::pushEvent( event ); + + SDL_Event sdlEvent{}; + sdlEvent.type = SDL_EVENT_MOUSE_WHEEL; + sdlEvent.wheel.windowID = event.WinID; + sdlEvent.wheel.x = event.wheel.x; + sdlEvent.wheel.y = event.wheel.y; + sdlEvent.wheel.direction = event.wheel.direction == InputEvent::WheelEvent::Normal + ? SDL_MOUSEWHEEL_NORMAL + : SDL_MOUSEWHEEL_FLIPPED; +#if SDL_VERSION_ATLEAST( 3, 2, 12 ) + sdlEvent.wheel.integer_x = static_cast( event.wheel.x ); + sdlEvent.wheel.integer_y = static_cast( event.wheel.y ); +#endif + sendEvent( sdlEvent ); + return true; +} + std::string InputSDL::getKeyName( const Keycode& keycode ) const { return std::string( SDL_GetKeyName( static_cast( keycode ) ) ); } @@ -144,9 +172,11 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) { case SDL_EVENT_WINDOW_RESIZED: { event.Type = InputEvent::VideoResize; event.WinID = SDLEvent.window.windowID; - mDPIScale = mWindow->getScale(); - event.resize.w = static_cast( SDLEvent.window.data1 * mDPIScale ); - event.resize.h = static_cast( SDLEvent.window.data2 * mDPIScale ); + const Float eventWindowScale = getEventWindowScale( mWindow, SDLEvent.window.windowID ); + if ( SDLEvent.window.windowID == mWindow->getWindowID() ) + mDPIScale = eventWindowScale; + event.resize.w = static_cast( SDLEvent.window.data1 * eventWindowScale ); + event.resize.h = static_cast( SDLEvent.window.data2 * eventWindowScale ); break; } case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: { @@ -267,44 +297,50 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) { break; } case SDL_EVENT_MOUSE_MOTION: { + const Float eventWindowScale = getEventWindowScale( mWindow, SDLEvent.motion.windowID ); event.Type = InputEvent::MouseMotion; event.motion.which = SDLEvent.motion.windowID; event.motion.state = SDLEvent.motion.state; - event.motion.x = static_cast( SDLEvent.motion.x * mDPIScale ); - event.motion.y = static_cast( SDLEvent.motion.y * mDPIScale ); - event.motion.xrel = static_cast( SDLEvent.motion.xrel * mDPIScale ); - event.motion.yrel = static_cast( SDLEvent.motion.yrel * mDPIScale ); + event.motion.x = static_cast( SDLEvent.motion.x * eventWindowScale ); + event.motion.y = static_cast( SDLEvent.motion.y * eventWindowScale ); + event.motion.xrel = static_cast( SDLEvent.motion.xrel * eventWindowScale ); + event.motion.yrel = static_cast( SDLEvent.motion.yrel * eventWindowScale ); event.WinID = SDLEvent.motion.windowID; break; } case SDL_EVENT_MOUSE_BUTTON_DOWN: { + const Float eventWindowScale = getEventWindowScale( mWindow, SDLEvent.button.windowID ); event.Type = InputEvent::MouseButtonDown; event.button.button = SDLEvent.button.button; event.button.which = SDLEvent.button.windowID; event.button.state = SDLEvent.button.down ? 1 : 0; - event.button.x = static_cast( SDLEvent.button.x * mDPIScale ); - event.button.y = static_cast( SDLEvent.button.y * mDPIScale ); + event.button.x = static_cast( SDLEvent.button.x * eventWindowScale ); + event.button.y = static_cast( SDLEvent.button.y * eventWindowScale ); event.WinID = SDLEvent.button.windowID; break; } case SDL_EVENT_MOUSE_BUTTON_UP: { + const Float eventWindowScale = getEventWindowScale( mWindow, SDLEvent.button.windowID ); event.Type = InputEvent::MouseButtonUp; event.button.button = SDLEvent.button.button; event.button.which = SDLEvent.button.windowID; event.button.state = SDLEvent.button.down ? 1 : 0; - event.button.x = static_cast( SDLEvent.button.x * mDPIScale ); - event.button.y = static_cast( SDLEvent.button.y * mDPIScale ); + event.button.x = static_cast( SDLEvent.button.x * eventWindowScale ); + event.button.y = static_cast( SDLEvent.button.y * eventWindowScale ); event.WinID = SDLEvent.button.windowID; break; } case SDL_EVENT_MOUSE_WHEEL: { Uint8 button; + const Float eventWindowScale = getEventWindowScale( mWindow, SDLEvent.wheel.windowID ); #if SDL_VERSION_ATLEAST( 3, 2, 12 ) const Sint32 integerX = SDLEvent.wheel.integer_x; const Sint32 integerY = SDLEvent.wheel.integer_y; #else - const Sint32 integerX = x > 0.f ? 1 : ( x < 0.f ? -1 : 0 ); - const Sint32 integerY = y > 0.f ? 1 : ( y < 0.f ? -1 : 0 ); + const Sint32 integerX = + SDLEvent.wheel.x > 0.f ? 1 : ( SDLEvent.wheel.x < 0.f ? -1 : 0 ); + const Sint32 integerY = + SDLEvent.wheel.y > 0.f ? 1 : ( SDLEvent.wheel.y < 0.f ? -1 : 0 ); #endif if ( integerY == 0 && integerX == 0 ) @@ -324,8 +360,8 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) { // Get mouse position from the event (mouse_x, mouse_y are in window coordinates) event.button.button = button; - event.button.x = static_cast( SDLEvent.wheel.mouse_x * mDPIScale ); - event.button.y = static_cast( SDLEvent.wheel.mouse_y * mDPIScale ); + event.button.x = static_cast( SDLEvent.wheel.mouse_x * eventWindowScale ); + event.button.y = static_cast( SDLEvent.wheel.mouse_y * eventWindowScale ); event.button.which = SDLEvent.wheel.windowID; event.WinID = SDLEvent.wheel.windowID; diff --git a/src/eepp/window/backend/SDL3/inputsdl3.hpp b/src/eepp/window/backend/SDL3/inputsdl3.hpp index 87ca5b293..f6b3e90f2 100644 --- a/src/eepp/window/backend/SDL3/inputsdl3.hpp +++ b/src/eepp/window/backend/SDL3/inputsdl3.hpp @@ -30,6 +30,8 @@ class EE_API InputSDL : public Input { bool isMouseCaptured() const; + bool pushEvent( const InputEvent& event ); + std::string getKeyName( const Keycode& keycode ) const; Keycode getKeyFromName( const std::string& keycode ) const; diff --git a/src/eepp/window/input.cpp b/src/eepp/window/input.cpp index ddde23efd..d7f33088e 100644 --- a/src/eepp/window/input.cpp +++ b/src/eepp/window/input.cpp @@ -61,6 +61,12 @@ void Input::sendEvent( InputEvent* Event ) { } } +bool Input::pushEvent( const InputEvent& event ) { + InputEvent eventCopy( event ); + processEventForWindow( &eventCopy ); + return true; +} + void Input::processEvent( InputEvent* Event ) { mLastEvent.restart(); diff --git a/src/tests/unit_tests/uiscenenode_tests.cpp b/src/tests/unit_tests/uiscenenode_tests.cpp index b5d28f40a..38cd8615b 100644 --- a/src/tests/unit_tests/uiscenenode_tests.cpp +++ b/src/tests/unit_tests/uiscenenode_tests.cpp @@ -81,6 +81,56 @@ class TestUIApplication : public UIApplication { void tickOnce() { tick(); } }; +UTEST( UIApplication, RoutesNativeMouseWheelExactlyOnceToTargetWindow ) { + TestUIApplication app( WindowSettings( 320, 240, "Primary Wheel Routing", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + UIApplication::Settings( + Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1.f, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + ASSERT_TRUE( app.getWindow() != nullptr ); + auto* secondaryUI = + app.createWindow( WindowSettings( 240, 180, "Secondary Wheel Routing", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + ASSERT_TRUE( secondaryUI != nullptr ); + + auto* primaryInput = app.getWindow()->getInput(); + auto* secondaryInput = secondaryUI->getWindow()->getInput(); + const Uint32 targetWindowId = secondaryUI->getWindow()->getWindowID(); + int primaryWheelEvents = 0; + int secondaryWheelEvents = 0; + Vector2f secondaryWheelOffset; + Uint32 receivedWindowId = 0; + const Uint32 primaryCallback = primaryInput->pushCallback( [&]( InputEvent* event ) { + if ( event->Type == InputEvent::MouseWheel ) + ++primaryWheelEvents; + } ); + const Uint32 secondaryCallback = secondaryInput->pushCallback( [&]( InputEvent* event ) { + if ( event->Type == InputEvent::MouseWheel ) { + ++secondaryWheelEvents; + secondaryWheelOffset = { event->wheel.x, event->wheel.y }; + receivedWindowId = event->WinID; + } + } ); + + InputEvent event{}; + event.Type = InputEvent::MouseWheel; + event.WinID = targetWindowId; + event.wheel.x = 0.f; + event.wheel.y = -1.f; + event.wheel.direction = InputEvent::WheelEvent::Normal; + ASSERT_TRUE( primaryInput->pushEvent( event ) ); + Engine::instance()->updateInput(); + + EXPECT_EQ( primaryWheelEvents, 0 ); + EXPECT_EQ( secondaryWheelEvents, 1 ); + EXPECT_EQ( receivedWindowId, targetWindowId ); + EXPECT_EQ( secondaryWheelOffset.x, 0.f ); + EXPECT_EQ( secondaryWheelOffset.y, -1.f ); + primaryInput->popCallback( primaryCallback ); + secondaryInput->popCallback( secondaryCallback ); +} + UTEST( UIApplication, CreatesSecondaryWindowWithoutChangingAmbientScene ) { TestUIApplication app( WindowSettings( 320, 240, "Primary UI Context", WindowStyle::Default,