/***************************************************************************** * Copyright (c) 2014-2024 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "InputManager.h" #include "ShortcutIds.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace OpenRCT2::Ui; void InputManager::QueueInputEvent(const SDL_Event& e) { switch (e.type) { case SDL_JOYHATMOTION: { if (e.jhat.value != SDL_HAT_CENTERED) { InputEvent ie; ie.DeviceKind = InputDeviceKind::JoyHat; ie.Modifiers = SDL_GetModState(); ie.Button = e.jhat.value; ie.State = InputEventState::Down; QueueInputEvent(std::move(ie)); } break; } case SDL_JOYBUTTONDOWN: { InputEvent ie; ie.DeviceKind = InputDeviceKind::JoyButton; ie.Modifiers = SDL_GetModState(); ie.Button = e.jbutton.button; ie.State = InputEventState::Down; QueueInputEvent(std::move(ie)); break; } case SDL_JOYBUTTONUP: { InputEvent ie; ie.DeviceKind = InputDeviceKind::JoyButton; ie.Modifiers = SDL_GetModState(); ie.Button = e.jbutton.button; ie.State = InputEventState::Release; QueueInputEvent(std::move(ie)); break; } } } void InputManager::QueueInputEvent(InputEvent&& e) { _events.push(e); } void InputManager::CheckJoysticks() { constexpr uint32_t CHECK_INTERVAL_MS = 5000; auto tick = SDL_GetTicks(); if (tick > _lastJoystickCheck + CHECK_INTERVAL_MS) { _lastJoystickCheck = tick; _joysticks.clear(); auto numJoysticks = SDL_NumJoysticks(); for (auto i = 0; i < numJoysticks; i++) { auto joystick = SDL_JoystickOpen(i); if (joystick != nullptr) { _joysticks.push_back(joystick); } } } } void InputManager::Process() { CheckJoysticks(); HandleModifiers(); ProcessEvents(); ProcessHoldEvents(); HandleViewScrolling(); } void InputManager::HandleViewScrolling() { if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) return; auto& console = GetInGameConsole(); if (console.IsOpen()) return; // Shortcut scrolling auto mainWindow = WindowGetMain(); if (mainWindow != nullptr && (_viewScroll.x != 0 || _viewScroll.y != 0)) { WindowUnfollowSprite(*mainWindow); } InputScrollViewport(_viewScroll); // Mouse edge scrolling if (gConfigGeneral.EdgeScrolling) { if (InputGetState() != InputState::Normal) return; if (gInputPlaceObjectModifier & (PLACE_OBJECT_MODIFIER_SHIFT_Z | PLACE_OBJECT_MODIFIER_COPY_Z)) return; GameHandleEdgeScroll(); } } void InputManager::HandleModifiers() { auto modifiers = SDL_GetModState(); gInputPlaceObjectModifier = PLACE_OBJECT_MODIFIER_NONE; if (modifiers & KMOD_SHIFT) { gInputPlaceObjectModifier |= PLACE_OBJECT_MODIFIER_SHIFT_Z; } if (modifiers & KMOD_CTRL) { gInputPlaceObjectModifier |= PLACE_OBJECT_MODIFIER_COPY_Z; } if (modifiers & KMOD_ALT) { gInputPlaceObjectModifier |= 4; } #ifdef __MACOSX__ if (modifiers & KMOD_GUI) { gInputPlaceObjectModifier |= 8; } #endif if (gConfigGeneral.VirtualFloorStyle != VirtualFloorStyles::Off) { if (gInputPlaceObjectModifier & (PLACE_OBJECT_MODIFIER_COPY_Z | PLACE_OBJECT_MODIFIER_SHIFT_Z)) VirtualFloorEnable(); else VirtualFloorDisable(); } } void InputManager::ProcessEvents() { while (!_events.empty()) { const auto& e = _events.front(); Process(e); _events.pop(); } } void InputManager::Process(const InputEvent& e) { auto& shortcutManager = GetShortcutManager(); if (e.DeviceKind == InputDeviceKind::Keyboard) { auto& console = GetInGameConsole(); if (console.IsOpen()) { if (!shortcutManager.ProcessEventForSpecificShortcut(e, ShortcutId::DebugToggleConsole)) { ProcessInGameConsole(e); } return; } if (gChatOpen) { ProcessChat(e); return; } if (e.DeviceKind == InputDeviceKind::Keyboard) { auto w = WindowFindByClass(WindowClass::Textinput); if (w != nullptr) { if (e.State == InputEventState::Release) { OpenRCT2::Ui::Windows::WindowTextInputKey(w, e.Button); } return; } if (OpenRCT2::Ui::Windows::IsUsingWidgetTextBox()) { return; } } } shortcutManager.ProcessEvent(e); } void InputManager::ProcessInGameConsole(const InputEvent& e) { if (e.DeviceKind == InputDeviceKind::Keyboard && e.State == InputEventState::Release) { auto input = ConsoleInput::None; switch (e.Button) { case SDLK_ESCAPE: input = ConsoleInput::LineClear; break; case SDLK_RETURN: case SDLK_KP_ENTER: input = ConsoleInput::LineExecute; break; case SDLK_UP: input = ConsoleInput::HistoryPrevious; break; case SDLK_DOWN: input = ConsoleInput::HistoryNext; break; case SDLK_PAGEUP: input = ConsoleInput::ScrollPrevious; break; case SDLK_PAGEDOWN: input = ConsoleInput::ScrollNext; break; } if (input != ConsoleInput::None) { auto& console = GetInGameConsole(); console.Input(input); } } } void InputManager::ProcessChat(const InputEvent& e) { if (e.DeviceKind == InputDeviceKind::Keyboard && e.State == InputEventState::Down) { auto input = ChatInput::None; switch (e.Button) { case SDLK_ESCAPE: input = ChatInput::Close; break; case SDLK_RETURN: case SDLK_KP_ENTER: input = ChatInput::Send; break; } if (input != ChatInput::None) { ChatInput(input); } } } void InputManager::ProcessHoldEvents() { // Get mouse state _mouseState = SDL_GetMouseState(nullptr, nullptr); // Get keyboard state int numkeys; auto keys = SDL_GetKeyboardState(&numkeys); _keyboardState.resize(numkeys); std::memcpy(_keyboardState.data(), keys, numkeys); // Check view scroll shortcuts _viewScroll.x = 0; _viewScroll.y = 0; if (!HasTextInputFocus()) { auto& shortcutManager = GetShortcutManager(); if (!shortcutManager.IsPendingShortcutChange()) { ProcessViewScrollEvent(ShortcutId::ViewScrollUp, { 0, -1 }); ProcessViewScrollEvent(ShortcutId::ViewScrollDown, { 0, 1 }); ProcessViewScrollEvent(ShortcutId::ViewScrollLeft, { -1, 0 }); ProcessViewScrollEvent(ShortcutId::ViewScrollRight, { 1, 0 }); } } } void InputManager::ProcessViewScrollEvent(std::string_view shortcutId, const ScreenCoordsXY& delta) { auto& shortcutManager = GetShortcutManager(); auto shortcut = shortcutManager.GetShortcut(shortcutId); if (shortcut != nullptr && GetState(*shortcut)) { _viewScroll.x += delta.x; _viewScroll.y += delta.y; } } bool InputManager::GetState(const RegisteredShortcut& shortcut) const { for (const auto& i : shortcut.Current) { if (GetState(i)) { return true; } } return false; } bool InputManager::GetState(const ShortcutInput& shortcut) const { constexpr uint32_t UsefulModifiers = KMOD_SHIFT | KMOD_CTRL | KMOD_ALT | KMOD_GUI; auto modifiers = SDL_GetModState() & UsefulModifiers; if ((shortcut.Modifiers & UsefulModifiers) == modifiers) { switch (shortcut.Kind) { case InputDeviceKind::Mouse: { if (_mouseState & (1 << shortcut.Button)) { return true; } break; } case InputDeviceKind::Keyboard: { auto scanCode = static_cast(SDL_GetScancodeFromKey(shortcut.Button)); if (scanCode < _keyboardState.size() && _keyboardState[scanCode]) { return true; } break; } case InputDeviceKind::JoyButton: { for (auto* joystick : _joysticks) { if (SDL_JoystickGetButton(joystick, shortcut.Button)) { return true; } } break; } case InputDeviceKind::JoyHat: { for (auto* joystick : _joysticks) { auto numHats = SDL_JoystickNumHats(joystick); for (int i = 0; i < numHats; i++) { auto hat = SDL_JoystickGetHat(joystick, i); if (hat & shortcut.Button) { return true; } } } break; } } } return false; } bool InputManager::HasTextInputFocus() const { if (OpenRCT2::Ui::Windows::IsUsingWidgetTextBox() || gChatOpen) return true; auto w = WindowFindByClass(WindowClass::Textinput); if (w != nullptr) return true; auto& console = GetInGameConsole(); if (console.IsOpen()) return true; return false; }