From 497cf9ea6cb0c835edbb09bf9ce07adc02155d73 Mon Sep 17 00:00:00 2001 From: caly Date: Sat, 22 Aug 2026 15:32:27 +0300 Subject: [PATCH] Scale mouse input for high-density windows --- src/platform/sdl_event_converter.cc | 3 +- src/runtime/roblox_input_router.cc | 40 ++++++++++++++++++------- tests/roblox_input_router_test.cc | 46 +++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/platform/sdl_event_converter.cc b/src/platform/sdl_event_converter.cc index d59cd6c..6fc1074 100644 --- a/src/platform/sdl_event_converter.cc +++ b/src/platform/sdl_event_converter.cc @@ -20,7 +20,8 @@ bool ConvertSdlEvent(SDL_Window* window, const SDL_Event& source, destination->payload = QuitEvent{}; return true; case SDL_EVENT_WINDOW_RESIZED: - case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: { + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: { WindowResizedEvent resized; if (!SDL_GetWindowSize(window, &resized.logical_width, &resized.logical_height) || diff --git a/src/runtime/roblox_input_router.cc b/src/runtime/roblox_input_router.cc index fbdb3ff..ec8cc8d 100644 --- a/src/runtime/roblox_input_router.cc +++ b/src/runtime/roblox_input_router.cc @@ -32,6 +32,12 @@ RobloxInputDispatchResult Result(RobloxInputDispatchState state, return {state, kind, std::move(status)}; } +float ScaleToSurfacePixels(float coordinate, int32_t logical_extent, + int32_t pixel_extent) { + return coordinate * static_cast(pixel_extent) / + static_cast(logical_extent); +} + } // namespace AndroidKeyMapping MapSdlKeyToAndroid(uint32_t sdl_scancode) { @@ -493,11 +499,19 @@ RobloxInputDispatchResult RobloxInputRouter::HandleMouseMotionLocked( RobloxInputEventKind::kMouseMotion, Unsupported("native mouse motion is unavailable")); } - mouse_x_ = event.x; - mouse_y_ = event.y; - return NativeResultLocked(sink_.mouse_move(sink_.context, event.x, event.y, - event.delta_x, event.delta_y), - RobloxInputEventKind::kMouseMotion); + mouse_x_ = ScaleToSurfacePixels(event.x, snapshot_.viewport.logical_width, + snapshot_.viewport.pixel_width); + mouse_y_ = ScaleToSurfacePixels(event.y, snapshot_.viewport.logical_height, + snapshot_.viewport.pixel_height); + const float delta_x = ScaleToSurfacePixels( + event.delta_x, snapshot_.viewport.logical_width, + snapshot_.viewport.pixel_width); + const float delta_y = ScaleToSurfacePixels( + event.delta_y, snapshot_.viewport.logical_height, + snapshot_.viewport.pixel_height); + return NativeResultLocked( + sink_.mouse_move(sink_.context, mouse_x_, mouse_y_, delta_x, delta_y), + RobloxInputEventKind::kMouseMotion); } RobloxInputDispatchResult RobloxInputRouter::HandleMouseButtonLocked( @@ -513,9 +527,11 @@ RobloxInputDispatchResult RobloxInputRouter::HandleMouseButtonLocked( RobloxInputEventKind::kMouseButton, Unsupported("SDL mouse button has no Android mapping")); } - mouse_x_ = event.x; - mouse_y_ = event.y; - Status status = sink_.mouse_button(sink_.context, event.x, event.y, + mouse_x_ = ScaleToSurfacePixels(event.x, snapshot_.viewport.logical_width, + snapshot_.viewport.pixel_width); + mouse_y_ = ScaleToSurfacePixels(event.y, snapshot_.viewport.logical_height, + snapshot_.viewport.pixel_height); + Status status = sink_.mouse_button(sink_.context, mouse_x_, mouse_y_, event.pressed, android_button); if (status.ok()) { const auto active = std::find(active_mouse_buttons_.begin(), @@ -538,8 +554,12 @@ RobloxInputDispatchResult RobloxInputRouter::HandleMouseWheelLocked( RobloxInputEventKind::kMouseWheel, Unsupported("vertical native mouse wheel is unavailable")); } - mouse_x_ = std::max(0.0f, event.mouse_x); - mouse_y_ = std::max(0.0f, event.mouse_y); + mouse_x_ = ScaleToSurfacePixels( + std::max(0.0f, event.mouse_x), snapshot_.viewport.logical_width, + snapshot_.viewport.pixel_width); + mouse_y_ = ScaleToSurfacePixels( + std::max(0.0f, event.mouse_y), snapshot_.viewport.logical_height, + snapshot_.viewport.pixel_height); return NativeResultLocked( sink_.mouse_wheel(sink_.context, mouse_x_, mouse_y_, event.delta_y), RobloxInputEventKind::kMouseWheel); diff --git a/tests/roblox_input_router_test.cc b/tests/roblox_input_router_test.cc index d703ca6..1cac679 100644 --- a/tests/roblox_input_router_test.cc +++ b/tests/roblox_input_router_test.cc @@ -204,8 +204,13 @@ TEST_F(RobloxInputRouterTest, RoutesMouseMotionButtonAndVerticalWheel) { .dispatched()); ASSERT_EQ(probe_.mouse_moves.size(), 1U); - EXPECT_FLOAT_EQ(probe_.mouse_moves[0].delta_x, 5.0f); + EXPECT_FLOAT_EQ(probe_.mouse_moves[0].x, 200.0f); + EXPECT_FLOAT_EQ(probe_.mouse_moves[0].y, 160.0f); + EXPECT_FLOAT_EQ(probe_.mouse_moves[0].delta_x, 10.0f); + EXPECT_FLOAT_EQ(probe_.mouse_moves[0].delta_y, -6.0f); ASSERT_EQ(probe_.mouse_buttons.size(), 1U); + EXPECT_FLOAT_EQ(probe_.mouse_buttons[0].x, 200.0f); + EXPECT_FLOAT_EQ(probe_.mouse_buttons[0].y, 160.0f); EXPECT_EQ(probe_.mouse_buttons[0].button, 1); ASSERT_EQ(probe_.mouse_wheels.size(), 1U); EXPECT_FLOAT_EQ(probe_.mouse_wheels[0].x, 0.0f); @@ -213,6 +218,41 @@ TEST_F(RobloxInputRouterTest, RoutesMouseMotionButtonAndVerticalWheel) { EXPECT_FLOAT_EQ(probe_.mouse_wheels[0].delta_y, -2.0f); } +TEST_F(RobloxInputRouterTest, + ScalesMouseCoordinatesAfterDisplayScaleChange) { + const RobloxInputDispatchResult resize = router_.HandleEvent( + Event(platform::WindowResizedEvent{1000, 800, 1250, 1000})); + ASSERT_EQ(resize.state, RobloxInputDispatchState::kStateUpdated); + + ASSERT_TRUE(router_ + .HandleEvent(Event(platform::MouseMotionEvent{ + 400.0f, 240.0f, 8.0f, -4.0f, 0})) + .dispatched()); + ASSERT_TRUE(router_ + .HandleEvent(Event(platform::MouseButtonEvent{ + true, SDL_BUTTON_LEFT, 1, 300.0f, 200.0f})) + .dispatched()); + ASSERT_TRUE(router_ + .HandleEvent(Event( + platform::MouseWheelEvent{0.0f, 2.0f, 600.0f, 400.0f})) + .dispatched()); + + ASSERT_EQ(probe_.mouse_moves.size(), 1U); + EXPECT_FLOAT_EQ(probe_.mouse_moves[0].x, 500.0f); + EXPECT_FLOAT_EQ(probe_.mouse_moves[0].y, 300.0f); + EXPECT_FLOAT_EQ(probe_.mouse_moves[0].delta_x, 10.0f); + EXPECT_FLOAT_EQ(probe_.mouse_moves[0].delta_y, -5.0f); + + ASSERT_EQ(probe_.mouse_buttons.size(), 1U); + EXPECT_FLOAT_EQ(probe_.mouse_buttons[0].x, 375.0f); + EXPECT_FLOAT_EQ(probe_.mouse_buttons[0].y, 250.0f); + + ASSERT_EQ(probe_.mouse_wheels.size(), 1U); + EXPECT_FLOAT_EQ(probe_.mouse_wheels[0].x, 750.0f); + EXPECT_FLOAT_EQ(probe_.mouse_wheels[0].y, 500.0f); + EXPECT_FLOAT_EQ(probe_.mouse_wheels[0].delta_y, 2.0f); +} + TEST_F(RobloxInputRouterTest, ScalesNormalizedTouchAndKeepsStablePointerIds) { EXPECT_TRUE(router_ .HandleEvent(Event(platform::TouchEvent{ @@ -493,8 +533,8 @@ TEST_F(RobloxInputRouterTest, ASSERT_TRUE(result.dispatched()); ASSERT_EQ(probe_.mouse_buttons.size(), 1U); - EXPECT_FLOAT_EQ(probe_.mouse_buttons[0].x, 321.0f); - EXPECT_FLOAT_EQ(probe_.mouse_buttons[0].y, 123.0f); + EXPECT_FLOAT_EQ(probe_.mouse_buttons[0].x, 642.0f); + EXPECT_FLOAT_EQ(probe_.mouse_buttons[0].y, 246.0f); EXPECT_TRUE(probe_.mouse_buttons[0].pressed); EXPECT_EQ(probe_.mouse_buttons[0].button, 0); }