mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-22 22:34:33 +01:00
Add ability to scroll the map with gamepad stick
This commit is contained in:
committed by
GitHub
parent
9fcbac56bb
commit
763242b14d
@@ -3823,3 +3823,10 @@ STR_6781 :Screenshots
|
||||
STR_6782 :Park notifications
|
||||
STR_6783 :Ride notifications
|
||||
STR_6784 :Guest notifications
|
||||
STR_6785 :Gamepad
|
||||
STR_6786 :Deadzone:
|
||||
STR_6787 :Analogue stick deadzone (minimum movement required)
|
||||
STR_6788 :Sensitivity:
|
||||
STR_6789 :Analogue stick sensitivity multiplier
|
||||
STR_6790 :Deadzone: {COMMA32}%
|
||||
STR_6791 :Sensitivity: {COMMA32}%
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
0.4.24 (in development)
|
||||
------------------------------------------------------------------------
|
||||
- Feature: [#24411] Vanilla scenarios now also have previews in the scenario selection window.
|
||||
- Feature: [#24616] Add ability to scroll map with gamepad sticks.
|
||||
- Feature: [#24662] Add optional screenshot argument for Z coord.
|
||||
- Improved: [#22684] The limit of 2000 animated tile elements has been removed.
|
||||
- Improved: [#23228] Landscape edge doors now animate opening and closing and play a sound.
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "ShortcutIds.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_gamecontroller.h>
|
||||
#include <cmath>
|
||||
#include <openrct2-ui/UiContext.h>
|
||||
#include <openrct2-ui/input/MouseInput.h>
|
||||
#include <openrct2-ui/input/ShortcutManager.h>
|
||||
@@ -22,6 +24,7 @@
|
||||
#include <openrct2/OpenRCT2.h>
|
||||
#include <openrct2/config/Config.h>
|
||||
#include <openrct2/interface/Chat.h>
|
||||
#include <openrct2/interface/Viewport.h>
|
||||
#include <openrct2/interface/Window.h>
|
||||
#include <openrct2/paint/VirtualFloor.h>
|
||||
#include <openrct2/ui/UiContext.h>
|
||||
@@ -38,6 +41,22 @@ void InputManager::QueueInputEvent(const SDL_Event& e)
|
||||
{
|
||||
switch (e.type)
|
||||
{
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
{
|
||||
// Process only the stick axes for scrolling (ignore triggers)
|
||||
if (e.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || e.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY
|
||||
|| e.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX || e.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY)
|
||||
{
|
||||
InputEvent ie;
|
||||
ie.DeviceKind = InputDeviceKind::JoyAxis;
|
||||
ie.Modifiers = SDL_GetModState();
|
||||
ie.Button = e.caxis.axis;
|
||||
ie.State = InputEventState::Down;
|
||||
ie.AxisValue = e.caxis.value;
|
||||
QueueInputEvent(std::move(ie));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYHATMOTION:
|
||||
{
|
||||
if (e.jhat.value != SDL_HAT_CENTERED)
|
||||
@@ -47,30 +66,44 @@ void InputManager::QueueInputEvent(const SDL_Event& e)
|
||||
ie.Modifiers = SDL_GetModState();
|
||||
ie.Button = e.jhat.value;
|
||||
ie.State = InputEventState::Down;
|
||||
ie.AxisValue = 0;
|
||||
QueueInputEvent(std::move(ie));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
{
|
||||
InputEvent ie;
|
||||
ie.DeviceKind = InputDeviceKind::JoyButton;
|
||||
ie.Modifiers = SDL_GetModState();
|
||||
ie.Button = e.jbutton.button;
|
||||
ie.Button = e.cbutton.button;
|
||||
ie.State = InputEventState::Down;
|
||||
ie.AxisValue = 0;
|
||||
QueueInputEvent(std::move(ie));
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
case SDL_JOYBUTTONUP:
|
||||
{
|
||||
InputEvent ie;
|
||||
ie.DeviceKind = InputDeviceKind::JoyButton;
|
||||
ie.Modifiers = SDL_GetModState();
|
||||
ie.Button = e.jbutton.button;
|
||||
ie.Button = e.cbutton.button;
|
||||
ie.State = InputEventState::Release;
|
||||
ie.AxisValue = 0;
|
||||
QueueInputEvent(std::move(ie));
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
case SDL_JOYDEVICEADDED:
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
{
|
||||
// Force joystick refresh on next check
|
||||
_lastJoystickCheck = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,22 +121,86 @@ void InputManager::CheckJoysticks()
|
||||
{
|
||||
_lastJoystickCheck = tick;
|
||||
|
||||
_joysticks.clear();
|
||||
_gameControllers.clear();
|
||||
auto numJoysticks = SDL_NumJoysticks();
|
||||
for (auto i = 0; i < numJoysticks; i++)
|
||||
{
|
||||
auto joystick = SDL_JoystickOpen(i);
|
||||
if (joystick != nullptr)
|
||||
if (SDL_IsGameController(i))
|
||||
{
|
||||
_joysticks.push_back(joystick);
|
||||
auto gameController = SDL_GameControllerOpen(i);
|
||||
if (gameController != nullptr)
|
||||
{
|
||||
_gameControllers.push_back(gameController);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::processAnalogueInput()
|
||||
{
|
||||
_analogueScroll.x = 0;
|
||||
_analogueScroll.y = 0;
|
||||
|
||||
const int32_t deadzone = Config::Get().general.gamepadDeadzone;
|
||||
const float sensitivity = Config::Get().general.gamepadSensitivity;
|
||||
|
||||
for (auto* gameController : _gameControllers)
|
||||
{
|
||||
if (gameController != nullptr)
|
||||
{
|
||||
int32_t stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX);
|
||||
int32_t stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY);
|
||||
|
||||
// Calculate the magnitude of the stick input vector
|
||||
float magnitude = std::sqrt(static_cast<float>(stickX * stickX + stickY * stickY));
|
||||
|
||||
if (magnitude > deadzone)
|
||||
{
|
||||
// Apply deadzone to the magnitude, creating a more linear response
|
||||
float adjustedMagnitude = (magnitude - deadzone) / (32767.0f - deadzone);
|
||||
adjustedMagnitude = std::min(adjustedMagnitude, 1.0f);
|
||||
|
||||
float rawX = (stickX / 32767.0f) * adjustedMagnitude;
|
||||
float rawY = (stickY / 32767.0f) * adjustedMagnitude;
|
||||
|
||||
// Use a quadratic curve for better fine control at low sensitivities
|
||||
float sensitivityCurve = sensitivity * sensitivity;
|
||||
float moveX = rawX * sensitivityCurve * 8.0f; // Reasonable base scale
|
||||
float moveY = rawY * sensitivityCurve * 8.0f;
|
||||
|
||||
// Accumulate the movement with fractional precision
|
||||
_analogueScrollAccumX += moveX;
|
||||
_analogueScrollAccumY += moveY;
|
||||
|
||||
// Extract integer movement for this frame
|
||||
float intPartX, intPartY;
|
||||
float fracX = std::modf(_analogueScrollAccumX, &intPartX);
|
||||
float fracY = std::modf(_analogueScrollAccumY, &intPartY);
|
||||
|
||||
int pixelsX = static_cast<int>(intPartX);
|
||||
int pixelsY = static_cast<int>(intPartY);
|
||||
|
||||
_analogueScrollAccumX = fracX;
|
||||
_analogueScrollAccumY = fracY;
|
||||
|
||||
_analogueScroll.x += pixelsX;
|
||||
_analogueScroll.y += pixelsY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::updateAnalogueScroll()
|
||||
{
|
||||
_viewScroll.x += _analogueScroll.x;
|
||||
_viewScroll.y += _analogueScroll.y;
|
||||
}
|
||||
|
||||
void InputManager::Process()
|
||||
{
|
||||
CheckJoysticks();
|
||||
processAnalogueInput();
|
||||
HandleModifiers();
|
||||
ProcessEvents();
|
||||
ProcessHoldEvents();
|
||||
@@ -119,13 +216,51 @@ void InputManager::HandleViewScrolling()
|
||||
if (console.IsOpen())
|
||||
return;
|
||||
|
||||
// Shortcut scrolling
|
||||
auto mainWindow = WindowGetMain();
|
||||
if (mainWindow != nullptr && (_viewScroll.x != 0 || _viewScroll.y != 0))
|
||||
|
||||
// Handle gamepad analogue scrolling with cursor-based viewport targeting
|
||||
if (_analogueScroll.x != 0 || _analogueScroll.y != 0)
|
||||
{
|
||||
WindowUnfollowSprite(*mainWindow);
|
||||
// Get cursor position to find target viewport
|
||||
const CursorState* cursorState = ContextGetCursorState();
|
||||
Viewport* targetViewport = ViewportFindFromPoint(cursorState->position);
|
||||
|
||||
WindowBase* targetWindow = nullptr;
|
||||
if (targetViewport != nullptr)
|
||||
{
|
||||
// Find the window that owns this viewport
|
||||
auto* windowMgr = GetWindowManager();
|
||||
targetWindow = windowMgr->GetOwner(targetViewport);
|
||||
}
|
||||
|
||||
// Fallback to main window if no viewport found under cursor
|
||||
if (targetWindow == nullptr)
|
||||
{
|
||||
targetWindow = mainWindow;
|
||||
}
|
||||
|
||||
if (targetWindow != nullptr)
|
||||
{
|
||||
// Only unfollow sprites for the main window or viewport windows
|
||||
// Don't unfollow for ride windows that might be following vehicles
|
||||
if (targetWindow == mainWindow || targetWindow->classification == WindowClass::Viewport)
|
||||
{
|
||||
WindowUnfollowSprite(*targetWindow);
|
||||
}
|
||||
InputScrollViewportSmooth(_analogueScroll, targetWindow);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle keyboard shortcut scrolling with edge-based scrolling (but ignore gamepad input)
|
||||
ScreenCoordsXY keyboardScroll = { _viewScroll.x - _analogueScroll.x, _viewScroll.y - _analogueScroll.y };
|
||||
if (keyboardScroll.x != 0 || keyboardScroll.y != 0)
|
||||
{
|
||||
if (mainWindow != nullptr)
|
||||
{
|
||||
WindowUnfollowSprite(*mainWindow);
|
||||
}
|
||||
InputScrollViewport(keyboardScroll);
|
||||
}
|
||||
InputScrollViewport(_viewScroll);
|
||||
|
||||
// Mouse edge scrolling
|
||||
if (Config::Get().general.EdgeScrolling)
|
||||
@@ -337,6 +472,8 @@ void InputManager::ProcessHoldEvents()
|
||||
ProcessViewScrollEvent(ShortcutId::kViewScrollLeft, { -1, 0 });
|
||||
ProcessViewScrollEvent(ShortcutId::kViewScrollRight, { 1, 0 });
|
||||
}
|
||||
|
||||
updateAnalogueScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,9 +527,11 @@ bool InputManager::GetState(const ShortcutInput& shortcut) const
|
||||
}
|
||||
case InputDeviceKind::JoyButton:
|
||||
{
|
||||
for (auto* joystick : _joysticks)
|
||||
for (auto* gameController : _gameControllers)
|
||||
{
|
||||
if (SDL_JoystickGetButton(joystick, shortcut.Button))
|
||||
// Get the underlying joystick to maintain compatibility with raw button numbers
|
||||
auto* joystick = SDL_GameControllerGetJoystick(gameController);
|
||||
if (joystick && SDL_JoystickGetButton(joystick, shortcut.Button))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -401,20 +540,31 @@ bool InputManager::GetState(const ShortcutInput& shortcut) const
|
||||
}
|
||||
case InputDeviceKind::JoyHat:
|
||||
{
|
||||
for (auto* joystick : _joysticks)
|
||||
for (auto* gameController : _gameControllers)
|
||||
{
|
||||
auto numHats = SDL_JoystickNumHats(joystick);
|
||||
for (int i = 0; i < numHats; i++)
|
||||
// Get the underlying joystick to maintain compatibility with hat functionality
|
||||
auto* joystick = SDL_GameControllerGetJoystick(gameController);
|
||||
if (joystick)
|
||||
{
|
||||
auto hat = SDL_JoystickGetHat(joystick, i);
|
||||
if (hat & shortcut.Button)
|
||||
auto numHats = SDL_JoystickNumHats(joystick);
|
||||
for (int i = 0; i < numHats; i++)
|
||||
{
|
||||
return true;
|
||||
auto hat = SDL_JoystickGetHat(joystick, i);
|
||||
if (hat & shortcut.Button)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InputDeviceKind::JoyAxis:
|
||||
{
|
||||
// analogue axes don't have a simple "pressed" state like buttons
|
||||
// Return false for shortcuts on analogue axes as they're handled differently
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <queue>
|
||||
#include <string_view>
|
||||
|
||||
typedef struct _SDL_Joystick SDL_Joystick;
|
||||
typedef struct _SDL_GameController SDL_GameController;
|
||||
typedef union SDL_Event SDL_Event;
|
||||
|
||||
namespace OpenRCT2::Ui
|
||||
@@ -27,6 +27,7 @@ namespace OpenRCT2::Ui
|
||||
Keyboard,
|
||||
JoyButton,
|
||||
JoyHat,
|
||||
JoyAxis,
|
||||
};
|
||||
|
||||
enum class InputEventState
|
||||
@@ -41,6 +42,7 @@ namespace OpenRCT2::Ui
|
||||
uint32_t Modifiers;
|
||||
uint32_t Button;
|
||||
InputEventState State;
|
||||
int16_t AxisValue; // For analogue stick values (-32768 to 32767)
|
||||
};
|
||||
|
||||
enum class ModifierKey : uint8_t
|
||||
@@ -56,14 +58,19 @@ namespace OpenRCT2::Ui
|
||||
{
|
||||
private:
|
||||
uint32_t _lastJoystickCheck{};
|
||||
std::vector<SDL_Joystick*> _joysticks;
|
||||
std::vector<SDL_GameController*> _gameControllers;
|
||||
std::queue<InputEvent> _events;
|
||||
ScreenCoordsXY _viewScroll;
|
||||
ScreenCoordsXY _analogueScroll; // analogue stick scroll values
|
||||
float _analogueScrollAccumX = 0.0f; // Fractional accumulator for X axis
|
||||
float _analogueScrollAccumY = 0.0f; // Fractional accumulator for Y axis
|
||||
uint32_t _mouseState{};
|
||||
std::vector<uint8_t> _keyboardState;
|
||||
uint8_t _modifierKeyState;
|
||||
|
||||
void CheckJoysticks();
|
||||
void processAnalogueInput();
|
||||
void updateAnalogueScroll();
|
||||
|
||||
void HandleViewScrolling();
|
||||
void HandleModifiers();
|
||||
|
||||
@@ -980,18 +980,13 @@ namespace OpenRCT2
|
||||
ScreenCoordsXY newScreenCoords;
|
||||
widgetScrollGetPart(*w, widget, screenCoords, newScreenCoords, &scroll_part, &scrollId);
|
||||
|
||||
if (scroll_part != SCROLL_PART_VIEW)
|
||||
WindowTooltipClose();
|
||||
else
|
||||
if (scroll_part == SCROLL_PART_VIEW)
|
||||
{
|
||||
w->OnScrollMouseOver(scrollId, newScreenCoords);
|
||||
InputUpdateTooltip(w, widgetIndex, screenCoords);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InputUpdateTooltip(w, widgetIndex, screenCoords);
|
||||
}
|
||||
|
||||
InputUpdateTooltip(w, widgetIndex, screenCoords);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1695,4 +1690,43 @@ namespace OpenRCT2
|
||||
gInputFlags.set(InputFlag::viewportScrolling);
|
||||
}
|
||||
}
|
||||
|
||||
void InputScrollViewportSmooth(const ScreenCoordsXY& scrollScreenCoords, WindowBase* targetWindow)
|
||||
{
|
||||
if (targetWindow == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Viewport* viewport = targetWindow->viewport;
|
||||
if (viewport == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetWindow->flags & WF_NO_SCROLLING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrollScreenCoords.x == 0 && scrollScreenCoords.y == 0)
|
||||
return;
|
||||
|
||||
// Apply smooth scrolling similar to mouse drag behavior
|
||||
// Use zoom-based scaling like mouse dragging does
|
||||
ScreenCoordsXY differentialCoords = scrollScreenCoords;
|
||||
|
||||
// Apply zoom scaling (same logic as mouse drag)
|
||||
const bool posX = differentialCoords.x > 0;
|
||||
const bool posY = differentialCoords.y > 0;
|
||||
differentialCoords.x = (viewport->zoom + 1).ApplyTo(-std::abs(differentialCoords.x));
|
||||
differentialCoords.y = (viewport->zoom + 1).ApplyTo(-std::abs(differentialCoords.y));
|
||||
differentialCoords.x = posX ? -differentialCoords.x : differentialCoords.x;
|
||||
differentialCoords.y = posY ? -differentialCoords.y : differentialCoords.y;
|
||||
|
||||
// Apply the movement (note: we don't invert for gamepad like mouse drag does)
|
||||
targetWindow->savedViewPos += differentialCoords;
|
||||
|
||||
gInputFlags.set(InputFlag::viewportScrolling);
|
||||
}
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -31,4 +31,6 @@ namespace OpenRCT2
|
||||
void StoreMouseInput(MouseState state, const ScreenCoordsXY& screenCoords);
|
||||
|
||||
void InputScrollViewport(const ScreenCoordsXY& screenCoords);
|
||||
void InputScrollViewportSmooth(const ScreenCoordsXY& screenCoords);
|
||||
void InputScrollViewportSmooth(const ScreenCoordsXY& screenCoords, WindowBase* targetWindow);
|
||||
} // namespace OpenRCT2
|
||||
|
||||
@@ -198,6 +198,13 @@ namespace OpenRCT2::Ui::Windows
|
||||
WIDX_TOUCH_ENHANCEMENTS,
|
||||
WIDX_HOTKEY_DROPDOWN,
|
||||
|
||||
// Gamepad
|
||||
WIDX_GAMEPAD_GROUP,
|
||||
WIDX_GAMEPAD_DEADZONE_LABEL,
|
||||
WIDX_GAMEPAD_DEADZONE,
|
||||
WIDX_GAMEPAD_SENSITIVITY_LABEL,
|
||||
WIDX_GAMEPAD_SENSITIVITY,
|
||||
|
||||
// Misc
|
||||
WIDX_TITLE_SEQUENCE_GROUP = WIDX_PAGE_START,
|
||||
WIDX_TITLE_SEQUENCE,
|
||||
@@ -351,6 +358,7 @@ namespace OpenRCT2::Ui::Windows
|
||||
);
|
||||
|
||||
constexpr int32_t kControlsGroupStart = 53;
|
||||
constexpr int32_t kGamepadGroupStart = kControlsGroupStart + 150;
|
||||
|
||||
static constexpr auto window_options_controls_widgets = makeWidgets(
|
||||
kMainOptionsWidgets,
|
||||
@@ -362,7 +370,14 @@ namespace OpenRCT2::Ui::Windows
|
||||
makeWidget({ 10, kControlsGroupStart + 75}, {290, 12}, WidgetType::checkbox, WindowColour::tertiary, STR_WINDOW_BUTTONS_ON_THE_LEFT, STR_WINDOW_BUTTONS_ON_THE_LEFT_TIP), // Window buttons on the left
|
||||
makeWidget({ 10, kControlsGroupStart + 90}, {290, 12}, WidgetType::checkbox, WindowColour::tertiary, STR_ENLARGED_UI, STR_ENLARGED_UI_TIP ),
|
||||
makeWidget({ 25, kControlsGroupStart + 105}, {275, 12}, WidgetType::checkbox, WindowColour::tertiary, STR_TOUCH_ENHANCEMENTS, STR_TOUCH_ENHANCEMENTS_TIP ),
|
||||
makeWidget({155, kControlsGroupStart + 120}, {145, 13}, WidgetType::button, WindowColour::secondary, STR_HOTKEY, STR_HOTKEY_TIP ) // Set hotkeys buttons
|
||||
makeWidget({155, kControlsGroupStart + 120}, {145, 13}, WidgetType::button, WindowColour::secondary, STR_HOTKEY, STR_HOTKEY_TIP ), // Set hotkeys buttons
|
||||
|
||||
// Gamepad group
|
||||
makeWidget({ 5, kGamepadGroupStart + 0}, {300, 45}, WidgetType::groupbox, WindowColour::secondary, STR_GAMEPAD_GROUP ), // Gamepad group
|
||||
makeWidget({ 10, kGamepadGroupStart + 13}, { 90, 12}, WidgetType::label, WindowColour::secondary, STR_GAMEPAD_DEADZONE_LABEL, STR_GAMEPAD_DEADZONE_TIP ), // Deadzone label
|
||||
makeWidget({105, kGamepadGroupStart + 13}, {190, 13}, WidgetType::scroll, WindowColour::secondary, SCROLL_HORIZONTAL, STR_GAMEPAD_DEADZONE_TOOLTIP_FORMAT), // Deadzone slider
|
||||
makeWidget({ 10, kGamepadGroupStart + 28}, { 90, 12}, WidgetType::label, WindowColour::secondary, STR_GAMEPAD_SENSITIVITY_LABEL, STR_GAMEPAD_SENSITIVITY_TIP ), // Sensitivity label
|
||||
makeWidget({105, kGamepadGroupStart + 28}, {190, 13}, WidgetType::scroll, WindowColour::secondary, SCROLL_HORIZONTAL, STR_GAMEPAD_SENSITIVITY_TOOLTIP_FORMAT) // Sensitivity slider
|
||||
);
|
||||
|
||||
constexpr int32_t kThemesGroupStart = 53;
|
||||
@@ -638,10 +653,12 @@ namespace OpenRCT2::Ui::Windows
|
||||
case WINDOW_OPTIONS_PAGE_AUDIO:
|
||||
AudioUpdate();
|
||||
break;
|
||||
case WINDOW_OPTIONS_PAGE_CONTROLS:
|
||||
ControlsUpdate();
|
||||
break;
|
||||
case WINDOW_OPTIONS_PAGE_DISPLAY:
|
||||
case WINDOW_OPTIONS_PAGE_RENDERING:
|
||||
case WINDOW_OPTIONS_PAGE_CULTURE:
|
||||
case WINDOW_OPTIONS_PAGE_CONTROLS:
|
||||
case WINDOW_OPTIONS_PAGE_MISC:
|
||||
case WINDOW_OPTIONS_PAGE_ADVANCED:
|
||||
default:
|
||||
@@ -655,10 +672,11 @@ namespace OpenRCT2::Ui::Windows
|
||||
{
|
||||
case WINDOW_OPTIONS_PAGE_AUDIO:
|
||||
return AudioScrollGetSize(scrollIndex);
|
||||
case WINDOW_OPTIONS_PAGE_CONTROLS:
|
||||
return ControlsScrollGetSize(scrollIndex);
|
||||
case WINDOW_OPTIONS_PAGE_DISPLAY:
|
||||
case WINDOW_OPTIONS_PAGE_RENDERING:
|
||||
case WINDOW_OPTIONS_PAGE_CULTURE:
|
||||
case WINDOW_OPTIONS_PAGE_CONTROLS:
|
||||
case WINDOW_OPTIONS_PAGE_MISC:
|
||||
case WINDOW_OPTIONS_PAGE_ADVANCED:
|
||||
default:
|
||||
@@ -671,6 +689,28 @@ namespace OpenRCT2::Ui::Windows
|
||||
if (page == WINDOW_OPTIONS_PAGE_ADVANCED)
|
||||
return AdvancedTooltip(widgetIndex, fallback);
|
||||
|
||||
if (page == WINDOW_OPTIONS_PAGE_CONTROLS)
|
||||
{
|
||||
if (widgetIndex == WIDX_GAMEPAD_DEADZONE)
|
||||
{
|
||||
const int32_t deadzone = Config::Get().general.gamepadDeadzone;
|
||||
const int32_t deadzonePercent = static_cast<int32_t>((deadzone / 32767.0f) * 100);
|
||||
|
||||
auto ft = Formatter();
|
||||
ft.Add<int32_t>(deadzonePercent);
|
||||
return { STR_GAMEPAD_DEADZONE_TOOLTIP_FORMAT, ft };
|
||||
}
|
||||
else if (widgetIndex == WIDX_GAMEPAD_SENSITIVITY)
|
||||
{
|
||||
const float sensitivity = Config::Get().general.gamepadSensitivity;
|
||||
const int32_t sensitivityDisplay = static_cast<int32_t>(sensitivity * 100);
|
||||
|
||||
auto ft = Formatter();
|
||||
ft.Add<int32_t>(sensitivityDisplay);
|
||||
return { STR_GAMEPAD_SENSITIVITY_TOOLTIP_FORMAT, ft };
|
||||
}
|
||||
}
|
||||
|
||||
return WindowBase::OnTooltip(widgetIndex, fallback);
|
||||
}
|
||||
|
||||
@@ -1538,6 +1578,42 @@ namespace OpenRCT2::Ui::Windows
|
||||
}
|
||||
}
|
||||
|
||||
void ControlsUpdate()
|
||||
{
|
||||
const auto& deadzoneWidget = widgets[WIDX_GAMEPAD_DEADZONE];
|
||||
const auto& deadzoneScroll = scrolls[0];
|
||||
uint8_t deadzonePercent = GetScrollPercentage(deadzoneWidget, deadzoneScroll);
|
||||
int32_t deadzoneValue = static_cast<int32_t>((deadzonePercent / 100.0f) * 32767);
|
||||
if (deadzoneValue != Config::Get().general.gamepadDeadzone)
|
||||
{
|
||||
Config::Get().general.gamepadDeadzone = deadzoneValue;
|
||||
Config::Save();
|
||||
}
|
||||
|
||||
const auto& sensitivityWidget = widgets[WIDX_GAMEPAD_SENSITIVITY];
|
||||
const auto& sensitivityScroll = scrolls[1];
|
||||
uint8_t sensitivityPercent = GetScrollPercentage(sensitivityWidget, sensitivityScroll);
|
||||
float sensitivityValue = 0.5f + (sensitivityPercent / 100.0f) * 2.5f; // Map 0-100% to 0.5-3.0
|
||||
if (std::abs(sensitivityValue - Config::Get().general.gamepadSensitivity) > 0.01f)
|
||||
{
|
||||
Config::Get().general.gamepadSensitivity = sensitivityValue;
|
||||
Config::Save();
|
||||
}
|
||||
}
|
||||
|
||||
ScreenSize ControlsScrollGetSize(int32_t scrollIndex)
|
||||
{
|
||||
switch (scrollIndex)
|
||||
{
|
||||
case 0: // Deadzone slider
|
||||
return { 500, 0 }; // Range 0-500 (same as audio sliders)
|
||||
case 1: // Sensitivity slider
|
||||
return { 500, 0 }; // Range 0-500 (same as audio sliders)
|
||||
default:
|
||||
return { 0, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
ScreenSize AudioScrollGetSize(int32_t scrollIndex)
|
||||
{
|
||||
return { 500, 0 };
|
||||
@@ -1603,7 +1679,6 @@ namespace OpenRCT2::Ui::Windows
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Controls tab events
|
||||
|
||||
void ControlsMouseUp(WidgetIndex widgetIndex)
|
||||
{
|
||||
auto* windowMgr = Ui::GetWindowManager();
|
||||
@@ -1670,6 +1745,19 @@ namespace OpenRCT2::Ui::Windows
|
||||
SetCheckboxValue(WIDX_TOUCH_ENHANCEMENTS, Config::Get().interface.TouchEnhancements);
|
||||
|
||||
widgetSetEnabled(*this, WIDX_TOUCH_ENHANCEMENTS, Config::Get().interface.EnlargedUi);
|
||||
|
||||
// Initialize scroll positions for sliders only on first frame
|
||||
if (frame_no == 0)
|
||||
{
|
||||
// Convert deadzone (0-32767) to percentage (0-100), then to scroll position (0-500)
|
||||
uint8_t deadzonePercent = static_cast<uint8_t>((Config::Get().general.gamepadDeadzone / 32767.0f) * 100);
|
||||
InitializeScrollPosition(WIDX_GAMEPAD_DEADZONE, 0, deadzonePercent);
|
||||
|
||||
// Convert sensitivity (0.5-3.0) to percentage (0-100), then to scroll position (0-500)
|
||||
uint8_t sensitivityPercent = static_cast<uint8_t>(
|
||||
((Config::Get().general.gamepadSensitivity - 0.5f) / 2.5f) * 100);
|
||||
InitializeScrollPosition(WIDX_GAMEPAD_SENSITIVITY, 1, sensitivityPercent);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
@@ -222,6 +222,10 @@ namespace OpenRCT2::Config
|
||||
#endif // _DEBUG
|
||||
model->TrapCursor = reader->GetBoolean("trap_cursor", false);
|
||||
model->AutoOpenShops = reader->GetBoolean("auto_open_shops", false);
|
||||
|
||||
// Gamepad settings
|
||||
model->gamepadDeadzone = reader->GetInt32("gamepad_deadzone", 3600);
|
||||
model->gamepadSensitivity = reader->GetFloat("gamepad_sensitivity", 1.5f);
|
||||
model->ScenarioUnlockingEnabled = reader->GetBoolean("scenario_unlocking_enabled", true);
|
||||
model->ScenarioHideMegaPark = reader->GetBoolean("scenario_hide_mega_park", true);
|
||||
model->LastSaveGameDirectory = reader->GetString("last_game_directory", "");
|
||||
@@ -313,6 +317,10 @@ namespace OpenRCT2::Config
|
||||
writer->WriteBoolean("multithreading", model->MultiThreading);
|
||||
writer->WriteBoolean("trap_cursor", model->TrapCursor);
|
||||
writer->WriteBoolean("auto_open_shops", model->AutoOpenShops);
|
||||
|
||||
// Gamepad settings
|
||||
writer->WriteInt32("gamepad_deadzone", model->gamepadDeadzone);
|
||||
writer->WriteFloat("gamepad_sensitivity", model->gamepadSensitivity);
|
||||
writer->WriteBoolean("scenario_unlocking_enabled", model->ScenarioUnlockingEnabled);
|
||||
writer->WriteBoolean("scenario_hide_mega_park", model->ScenarioHideMegaPark);
|
||||
writer->WriteString("last_game_directory", model->LastSaveGameDirectory);
|
||||
|
||||
@@ -85,6 +85,10 @@ namespace OpenRCT2::Config
|
||||
bool InvertViewportDrag;
|
||||
bool ZoomToCursor;
|
||||
|
||||
// Gamepad
|
||||
int32_t gamepadDeadzone;
|
||||
float gamepadSensitivity;
|
||||
|
||||
// Miscellaneous
|
||||
bool PlayIntro;
|
||||
int32_t WindowSnapProximity;
|
||||
|
||||
@@ -1880,7 +1880,7 @@ namespace OpenRCT2
|
||||
GfxSetDirtyBlocks(invalidRect);
|
||||
}
|
||||
|
||||
static Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords)
|
||||
Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords)
|
||||
{
|
||||
auto* windowMgr = Ui::GetWindowManager();
|
||||
WindowBase* w = windowMgr->FindFromPoint(screenCoords);
|
||||
|
||||
@@ -223,6 +223,7 @@ namespace OpenRCT2
|
||||
std::optional<CoordsXY> ScreenGetMapXYQuadrantWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant);
|
||||
std::optional<CoordsXY> ScreenGetMapXYSide(const ScreenCoordsXY& screenCoords, uint8_t* side);
|
||||
std::optional<CoordsXY> ScreenGetMapXYSideWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side);
|
||||
Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords);
|
||||
|
||||
ScreenCoordsXY Translate3DTo2DWithZ(int32_t rotation, const CoordsXYZ& pos);
|
||||
|
||||
|
||||
@@ -1728,6 +1728,15 @@ enum : StringId
|
||||
STR_OBJECT_SELECTION_CLIMATE = 6743,
|
||||
STR_CLIMATE_WEATHER_PERCENT = 6744,
|
||||
|
||||
// Gamepad settings
|
||||
STR_GAMEPAD_GROUP = 6785,
|
||||
STR_GAMEPAD_DEADZONE_LABEL = 6786,
|
||||
STR_GAMEPAD_DEADZONE_TIP = 6787,
|
||||
STR_GAMEPAD_SENSITIVITY_LABEL = 6788,
|
||||
STR_GAMEPAD_SENSITIVITY_TIP = 6789,
|
||||
STR_GAMEPAD_DEADZONE_TOOLTIP_FORMAT = 6790,
|
||||
STR_GAMEPAD_SENSITIVITY_TOOLTIP_FORMAT = 6791,
|
||||
|
||||
// Have to include resource strings (from scenarios and objects) for the time being now that language is partially working
|
||||
/* MAX_STR_COUNT = 32768 */ // MAX_STR_COUNT - upper limit for number of strings, not the current count strings
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user