mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
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.
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Sint32>( event.wheel.x );
|
||||
sdlEvent.wheel.y = static_cast<Sint32>( 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Sint32>( event.wheel.x );
|
||||
sdlEvent.wheel.integer_y = static_cast<Sint32>( event.wheel.y );
|
||||
#endif
|
||||
sendEvent( sdlEvent );
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string InputSDL::getKeyName( const Keycode& keycode ) const {
|
||||
return std::string( SDL_GetKeyName( static_cast<SDL_Keycode>( 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<int>( SDLEvent.window.data1 * mDPIScale );
|
||||
event.resize.h = static_cast<int>( 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<int>( SDLEvent.window.data1 * eventWindowScale );
|
||||
event.resize.h = static_cast<int>( 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<Int16>( SDLEvent.motion.x * mDPIScale );
|
||||
event.motion.y = static_cast<Int16>( SDLEvent.motion.y * mDPIScale );
|
||||
event.motion.xrel = static_cast<Int16>( SDLEvent.motion.xrel * mDPIScale );
|
||||
event.motion.yrel = static_cast<Int16>( SDLEvent.motion.yrel * mDPIScale );
|
||||
event.motion.x = static_cast<Int16>( SDLEvent.motion.x * eventWindowScale );
|
||||
event.motion.y = static_cast<Int16>( SDLEvent.motion.y * eventWindowScale );
|
||||
event.motion.xrel = static_cast<Int16>( SDLEvent.motion.xrel * eventWindowScale );
|
||||
event.motion.yrel = static_cast<Int16>( 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<Int16>( SDLEvent.button.x * mDPIScale );
|
||||
event.button.y = static_cast<Int16>( SDLEvent.button.y * mDPIScale );
|
||||
event.button.x = static_cast<Int16>( SDLEvent.button.x * eventWindowScale );
|
||||
event.button.y = static_cast<Int16>( 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<Int16>( SDLEvent.button.x * mDPIScale );
|
||||
event.button.y = static_cast<Int16>( SDLEvent.button.y * mDPIScale );
|
||||
event.button.x = static_cast<Int16>( SDLEvent.button.x * eventWindowScale );
|
||||
event.button.y = static_cast<Int16>( 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<Int16>( SDLEvent.wheel.mouse_x * mDPIScale );
|
||||
event.button.y = static_cast<Int16>( SDLEvent.wheel.mouse_y * mDPIScale );
|
||||
event.button.x = static_cast<Int16>( SDLEvent.wheel.mouse_x * eventWindowScale );
|
||||
event.button.y = static_cast<Int16>( SDLEvent.wheel.mouse_y * eventWindowScale );
|
||||
event.button.which = SDLEvent.wheel.windowID;
|
||||
event.WinID = SDLEvent.wheel.windowID;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user