From 526d5f529d7facc2e3d2be4bd1f5825b139b994e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Janiszewski?= Date: Sat, 14 Jun 2025 17:07:54 +0200 Subject: [PATCH] Feature: Add gamepad scrolling support This adds support for configuring gamepad for scrolling --- src/gfx_func.h | 1 + src/lang/english.txt | 19 ++++++++++ src/settingentry_gui.cpp | 5 +++ src/settings_type.h | 12 ++++++ src/table/settings/gui_settings.ini | 59 +++++++++++++++++++++++++++++ src/window.cpp | 53 ++++++++++++++++++++++++++ 6 files changed, 149 insertions(+) diff --git a/src/gfx_func.h b/src/gfx_func.h index 885f72ccf9..161d52ac9b 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -75,6 +75,7 @@ void HandleTextInput(std::string_view str, bool marked = false, std::optional insert_location = std::nullopt, std::optional replacement_end = std::nullopt); void HandleCtrlChanged(); void HandleMouseEvents(); +void HandleGamepadScrolling(int stick_x, int stick_y, int max_axis_value); void UpdateWindows(); void ChangeGameSpeed(bool enable_fast_forward); diff --git a/src/lang/english.txt b/src/lang/english.txt index 41531939f3..14616974e9 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1700,6 +1700,25 @@ STR_CONFIG_SETTING_SCROLLWHEEL_ZOOM :Zoom map STR_CONFIG_SETTING_SCROLLWHEEL_SCROLL :Scroll map STR_CONFIG_SETTING_SCROLLWHEEL_OFF :Off +STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION :Gamepad stick for scrolling: {STRING2} +STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION_HELPTEXT :Select which analog stick to use for map scrolling +###length 3 +STR_CONFIG_SETTING_GAMEPAD_STICK_DISABLED :Disabled +STR_CONFIG_SETTING_GAMEPAD_STICK_LEFT :Left stick +STR_CONFIG_SETTING_GAMEPAD_STICK_RIGHT :Right stick + +STR_CONFIG_SETTING_GAMEPAD_DEADZONE :Gamepad deadzone: {STRING2}% +STR_CONFIG_SETTING_GAMEPAD_DEADZONE_HELPTEXT :Minimum stick movement required before scrolling starts (0-100%) + +STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY :Gamepad sensitivity: {STRING2} +STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY_HELPTEXT :Control the sensitivity of gamepad analog stick scrolling + +STR_CONFIG_SETTING_GAMEPAD_INVERT_X :Invert gamepad X-axis: {STRING2} +STR_CONFIG_SETTING_GAMEPAD_INVERT_X_HELPTEXT :Invert the horizontal axis movement of the gamepad analog stick + +STR_CONFIG_SETTING_GAMEPAD_INVERT_Y :Invert gamepad Y-axis: {STRING2} +STR_CONFIG_SETTING_GAMEPAD_INVERT_Y_HELPTEXT :Invert the vertical axis movement of the gamepad analog stick + STR_CONFIG_SETTING_OSK_ACTIVATION :On screen keyboard: {STRING2} STR_CONFIG_SETTING_OSK_ACTIVATION_HELPTEXT :Select the method to open the on screen keyboard for entering text into editboxes only using the pointing device. This is meant for small devices without actual keyboard ###length 4 diff --git a/src/settingentry_gui.cpp b/src/settingentry_gui.cpp index 1aece65330..88504640bf 100644 --- a/src/settingentry_gui.cpp +++ b/src/settingentry_gui.cpp @@ -681,6 +681,11 @@ SettingsContainer &GetSettingsTree() * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */ viewports->Add(new SettingEntry("gui.scrollwheel_scrolling")); viewports->Add(new SettingEntry("gui.scrollwheel_multiplier")); + viewports->Add(new SettingEntry("gui.gamepad_stick_selection")); + viewports->Add(new SettingEntry("gui.gamepad_deadzone")); + viewports->Add(new SettingEntry("gui.gamepad_sensitivity")); + viewports->Add(new SettingEntry("gui.gamepad_invert_x")); + viewports->Add(new SettingEntry("gui.gamepad_invert_y")); #ifdef __APPLE__ /* We might need to emulate a right mouse button on mac */ viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation")); diff --git a/src/settings_type.h b/src/settings_type.h index 255038e537..ca94092b55 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -140,6 +140,13 @@ enum ScrollWheelScrollingSetting : uint8_t { SWS_OFF = 2 ///< Scroll wheel has no effect. }; +/** Settings related to gamepad stick selection. */ +enum GamepadStickSelection : uint8_t { + GSS_DISABLED = 0, ///< Gamepad scrolling disabled. + GSS_LEFT_STICK = 1, ///< Use left analog stick for scrolling. + GSS_RIGHT_STICK = 2, ///< Use right analog stick for scrolling. +}; + /** Settings related to the GUI and other stuff that is not saved in the savegame. */ struct GUISettings { bool sg_full_load_any; ///< new full load calculation, any cargo must be full read from pre v93 savegames @@ -183,6 +190,11 @@ struct GUISettings { uint8_t right_mouse_btn_emulation; ///< should we emulate right mouse clicking? uint8_t scrollwheel_scrolling; ///< scrolling using the scroll wheel? uint8_t scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS? + uint8_t gamepad_deadzone; ///< deadzone for gamepad analog sticks (0-100) + uint8_t gamepad_sensitivity; ///< sensitivity multiplier for gamepad scrolling + bool gamepad_invert_x; ///< invert X axis for gamepad scrolling? + bool gamepad_invert_y; ///< invert Y axis for gamepad scrolling? + uint8_t gamepad_stick_selection; ///< which stick to use for scrolling (left/right/disabled) bool timetable_arrival_departure; ///< show arrivals and departures in vehicle timetables RightClickClose right_click_wnd_close; ///< close window with right click bool pause_on_newgame; ///< whether to start new games paused or not diff --git a/src/table/settings/gui_settings.ini b/src/table/settings/gui_settings.ini index 3d9ee1b75b..0f3079b080 100644 --- a/src/table/settings/gui_settings.ini +++ b/src/table/settings/gui_settings.ini @@ -912,3 +912,62 @@ post_cb = [](auto) { SetupWidgetDimensions(); ReInitAllWindows(true); } cat = SC_BASIC startup = true +[SDTC_VAR] +var = gui.gamepad_deadzone +type = SLE_UINT8 +flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync +def = 10 +min = 0 +max = 100 +interval = 5 +str = STR_CONFIG_SETTING_GAMEPAD_DEADZONE +strhelp = STR_CONFIG_SETTING_GAMEPAD_DEADZONE_HELPTEXT +strval = STR_JUST_COMMA +cat = SC_BASIC +startup = true + +[SDTC_VAR] +var = gui.gamepad_sensitivity +type = SLE_UINT8 +flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync +def = 10 +min = 1 +max = 100 +interval = 5 +str = STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY +strhelp = STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY_HELPTEXT +strval = STR_JUST_COMMA +cat = SC_BASIC +startup = true + +[SDTC_BOOL] +var = gui.gamepad_invert_x +flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync +def = false +str = STR_CONFIG_SETTING_GAMEPAD_INVERT_X +strhelp = STR_CONFIG_SETTING_GAMEPAD_INVERT_X_HELPTEXT +cat = SC_BASIC +startup = true + +[SDTC_BOOL] +var = gui.gamepad_invert_y +flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync +def = false +str = STR_CONFIG_SETTING_GAMEPAD_INVERT_Y +strhelp = STR_CONFIG_SETTING_GAMEPAD_INVERT_Y_HELPTEXT +cat = SC_BASIC +startup = true + +[SDTC_VAR] +var = gui.gamepad_stick_selection +type = SLE_UINT8 +flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync, SettingFlag::GuiDropdown +def = GSS_LEFT_STICK +min = GSS_DISABLED +max = GSS_RIGHT_STICK +str = STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION +strhelp = STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION_HELPTEXT +strval = STR_CONFIG_SETTING_GAMEPAD_STICK_DISABLED +cat = SC_BASIC +startup = true + diff --git a/src/window.cpp b/src/window.cpp index 6ae18eda28..65e9dbb2bd 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -3572,3 +3572,56 @@ void PickerWindowBase::Close([[maybe_unused]] int data) ResetObjectToPlace(); this->Window::Close(); } + +/** + * Process gamepad analog stick input for viewport scrolling. + * This is a common function that can be used by any video driver that supports gamepads. + * @param stick_x Raw analog stick X value (typically -32768 to 32767 range) + * @param stick_y Raw analog stick Y value (typically -32768 to 32767 range) + * @param max_axis_value Maximum value for the analog stick axes (e.g., 32767 for SDL2) + */ +void HandleGamepadScrolling(int stick_x, int stick_y, int max_axis_value) +{ + /* Skip if gamepad stick selection is disabled */ + if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) { + return; + } + + /* Apply deadzone (convert percentage to axis range) */ + const int deadzone = (_settings_client.gui.gamepad_deadzone * max_axis_value) / 100; + + if (abs(stick_x) < deadzone) stick_x = 0; + if (abs(stick_y) < deadzone) stick_y = 0; + + /* Skip if no movement after deadzone */ + if (stick_x == 0 && stick_y == 0) { + return; + } + + /* Calculate scroll delta with sensitivity */ + float sensitivity = _settings_client.gui.gamepad_sensitivity / 10.0f; + int delta_x = (int)(stick_x * sensitivity / (max_axis_value / 16)); // Scale down from axis range + int delta_y = (int)(stick_y * sensitivity / (max_axis_value / 16)); + + /* Apply axis inversion */ + if (_settings_client.gui.gamepad_invert_x) delta_x = -delta_x; + if (_settings_client.gui.gamepad_invert_y) delta_y = -delta_y; + + /* Skip if deltas are too small */ + if (abs(delta_x) < 1 && abs(delta_y) < 1) { + return; + } + + /* Apply scrolling to the main viewport */ + if (_game_mode != GM_MENU && _game_mode != GM_BOOTSTRAP) { + Window *main_window = GetMainWindow(); + if (main_window != nullptr && main_window->viewport != nullptr) { + /* Cancel vehicle following when gamepad scrolling */ + main_window->viewport->CancelFollow(*main_window); + + /* Apply the scroll using the same method as keyboard scrolling */ + main_window->viewport->dest_scrollpos_x += ScaleByZoom(delta_x, main_window->viewport->zoom); + main_window->viewport->dest_scrollpos_y += ScaleByZoom(delta_y, main_window->viewport->zoom); + } + } +}