diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp index cb4ec78b503..c7ce4fd082c 100644 --- a/src/engine/client/input.cpp +++ b/src/engine/client/input.cpp @@ -32,16 +32,41 @@ // for platform specific features that aren't available or are broken in SDL #include +#ifdef KeyPress +#undef KeyPress // Undo pollution from X11/Xlib.h included by SDL_syswm.h on Linux +#endif + +static void AssertKeyValid(int Key) +{ + if(Key < KEY_FIRST || Key >= KEY_LAST) + { + char aError[32]; + str_format(aError, sizeof(aError), "Key invalid: %d", Key); + dbg_assert(false, aError); + } +} void CInput::AddKeyEvent(int Key, int Flags) { + AssertKeyValid(Key); dbg_assert((Flags & (FLAG_PRESS | FLAG_RELEASE)) != 0 && (Flags & ~(FLAG_PRESS | FLAG_RELEASE)) == 0, "Flags invalid"); + CEvent Event; Event.m_Key = Key; Event.m_Flags = Flags; Event.m_aText[0] = '\0'; Event.m_InputCount = m_InputCounter; m_vInputEvents.emplace_back(Event); + + if(Flags & IInput::FLAG_PRESS) + { + m_aCurrentKeyStates[Key] = true; + m_aFrameKeyStates[Key] = true; + } + if(Flags & IInput::FLAG_RELEASE) + { + m_aCurrentKeyStates[Key] = false; + } } void CInput::AddTextEvent(const char *pText) @@ -56,8 +81,8 @@ void CInput::AddTextEvent(const char *pText) CInput::CInput() { - mem_zero(m_aInputCount, sizeof(m_aInputCount)); - mem_zero(m_aInputState, sizeof(m_aInputState)); + std::fill(std::begin(m_aCurrentKeyStates), std::end(m_aCurrentKeyStates), false); + std::fill(std::begin(m_aFrameKeyStates), std::end(m_aFrameKeyStates), false); m_vInputEvents.reserve(32); m_LastUpdate = 0; @@ -353,8 +378,7 @@ void CInput::ConsumeEvents(std::function Consumer) co void CInput::Clear() { - mem_zero(m_aInputState, sizeof(m_aInputState)); - mem_zero(m_aInputCount, sizeof(m_aInputCount)); + std::fill(std::begin(m_aFrameKeyStates), std::end(m_aFrameKeyStates), false); m_vInputEvents.clear(); ClearTouchDeltas(); } @@ -364,11 +388,22 @@ float CInput::GetUpdateTime() const return m_UpdateTime; } -bool CInput::KeyState(int Key) const +bool CInput::KeyIsPressed(int Key) const { - if(Key < KEY_FIRST || Key >= KEY_LAST) - return false; - return m_aInputState[Key]; + AssertKeyValid(Key); + return m_aCurrentKeyStates[Key]; +} + +bool CInput::KeyPress(int Key) const +{ + AssertKeyValid(Key); + return m_aFrameKeyStates[Key]; +} + +const char *CInput::KeyName(int Key) const +{ + AssertKeyValid(Key); + return g_aaKeyStrings[Key]; } int CInput::FindKeyByName(const char *pKeyName) const @@ -391,56 +426,6 @@ int CInput::FindKeyByName(const char *pKeyName) const return KEY_UNKNOWN; } -void CInput::UpdateMouseState() -{ - const int MouseState = SDL_GetMouseState(nullptr, nullptr); - if(MouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) - m_aInputState[KEY_MOUSE_1] = 1; - if(MouseState & SDL_BUTTON(SDL_BUTTON_RIGHT)) - m_aInputState[KEY_MOUSE_2] = 1; - if(MouseState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) - m_aInputState[KEY_MOUSE_3] = 1; - if(MouseState & SDL_BUTTON(SDL_BUTTON_X1)) - m_aInputState[KEY_MOUSE_4] = 1; - if(MouseState & SDL_BUTTON(SDL_BUTTON_X2)) - m_aInputState[KEY_MOUSE_5] = 1; - if(MouseState & SDL_BUTTON(6)) - m_aInputState[KEY_MOUSE_6] = 1; - if(MouseState & SDL_BUTTON(7)) - m_aInputState[KEY_MOUSE_7] = 1; - if(MouseState & SDL_BUTTON(8)) - m_aInputState[KEY_MOUSE_8] = 1; - if(MouseState & SDL_BUTTON(9)) - m_aInputState[KEY_MOUSE_9] = 1; -} - -void CInput::UpdateJoystickState() -{ - if(!g_Config.m_InpControllerEnable) - return; - IJoystick *pJoystick = GetActiveJoystick(); - if(!pJoystick) - return; - - const float DeadZone = GetJoystickDeadzone(); - for(int Axis = 0; Axis < pJoystick->GetNumAxes(); Axis++) - { - const float Value = pJoystick->GetAxisValue(Axis); - const int LeftKey = KEY_JOY_AXIS_0_LEFT + 2 * Axis; - const int RightKey = LeftKey + 1; - m_aInputState[LeftKey] = Value <= -DeadZone; - m_aInputState[RightKey] = Value >= DeadZone; - } - - for(int Hat = 0; Hat < pJoystick->GetNumHats(); Hat++) - { - int HatKeys[2]; - pJoystick->GetHatValue(Hat, HatKeys); - for(int Key = KEY_JOY_HAT0_UP + Hat * NUM_JOYSTICK_BUTTONS_PER_HAT; Key <= KEY_JOY_HAT0_DOWN + Hat * NUM_JOYSTICK_BUTTONS_PER_HAT; Key++) - m_aInputState[Key] = HatKeys[0] == Key || HatKeys[1] == Key; - } -} - void CInput::HandleJoystickAxisMotionEvent(const SDL_JoyAxisEvent &Event) { if(!g_Config.m_InpControllerEnable) @@ -455,27 +440,21 @@ void CInput::HandleJoystickAxisMotionEvent(const SDL_JoyAxisEvent &Event) const int RightKey = LeftKey + 1; const float DeadZone = GetJoystickDeadzone(); - if(Event.value <= SDL_JOYSTICK_AXIS_MIN * DeadZone && !m_aInputState[LeftKey]) + if(Event.value <= SDL_JOYSTICK_AXIS_MIN * DeadZone && !m_aCurrentKeyStates[LeftKey]) { - m_aInputState[LeftKey] = true; - m_aInputCount[LeftKey] = m_InputCounter; AddKeyEvent(LeftKey, IInput::FLAG_PRESS); } - else if(Event.value > SDL_JOYSTICK_AXIS_MIN * DeadZone && m_aInputState[LeftKey]) + else if(Event.value > SDL_JOYSTICK_AXIS_MIN * DeadZone && m_aCurrentKeyStates[LeftKey]) { - m_aInputState[LeftKey] = false; AddKeyEvent(LeftKey, IInput::FLAG_RELEASE); } - if(Event.value >= SDL_JOYSTICK_AXIS_MAX * DeadZone && !m_aInputState[RightKey]) + if(Event.value >= SDL_JOYSTICK_AXIS_MAX * DeadZone && !m_aCurrentKeyStates[RightKey]) { - m_aInputState[RightKey] = true; - m_aInputCount[RightKey] = m_InputCounter; AddKeyEvent(RightKey, IInput::FLAG_PRESS); } - else if(Event.value < SDL_JOYSTICK_AXIS_MAX * DeadZone && m_aInputState[RightKey]) + else if(Event.value < SDL_JOYSTICK_AXIS_MAX * DeadZone && m_aCurrentKeyStates[RightKey]) { - m_aInputState[RightKey] = false; AddKeyEvent(RightKey, IInput::FLAG_RELEASE); } } @@ -494,13 +473,10 @@ void CInput::HandleJoystickButtonEvent(const SDL_JoyButtonEvent &Event) if(Event.type == SDL_JOYBUTTONDOWN) { - m_aInputState[Key] = true; - m_aInputCount[Key] = m_InputCounter; AddKeyEvent(Key, IInput::FLAG_PRESS); } else if(Event.type == SDL_JOYBUTTONUP) { - m_aInputState[Key] = false; AddKeyEvent(Key, IInput::FLAG_RELEASE); } } @@ -520,19 +496,16 @@ void CInput::HandleJoystickHatMotionEvent(const SDL_JoyHatEvent &Event) for(int Key = KEY_JOY_HAT0_UP + Event.hat * NUM_JOYSTICK_BUTTONS_PER_HAT; Key <= KEY_JOY_HAT0_DOWN + Event.hat * NUM_JOYSTICK_BUTTONS_PER_HAT; Key++) { - if(Key != HatKeys[0] && Key != HatKeys[1] && m_aInputState[Key]) + if(Key != HatKeys[0] && Key != HatKeys[1] && m_aCurrentKeyStates[Key]) { - m_aInputState[Key] = false; AddKeyEvent(Key, IInput::FLAG_RELEASE); } } for(int CurrentKey : HatKeys) { - if(CurrentKey != KEY_UNKNOWN && !m_aInputState[CurrentKey]) + if(CurrentKey != KEY_UNKNOWN && !m_aCurrentKeyStates[CurrentKey]) { - m_aInputState[CurrentKey] = true; - m_aInputCount[CurrentKey] = m_InputCounter; AddKeyEvent(CurrentKey, IInput::FLAG_PRESS); } } @@ -627,7 +600,7 @@ void CInput::SetCompositionWindowPosition(float X, float Y, float H) SDL_SetTextInputRect(&Rect); } -static int TranslateScancode(const SDL_KeyboardEvent &KeyEvent) +static int TranslateKeyEventKey(const SDL_KeyboardEvent &KeyEvent) { // See SDL_Keymod for possible modifiers: // NONE = 0 @@ -645,20 +618,71 @@ static int TranslateScancode(const SDL_KeyboardEvent &KeyEvent) // Sum if you want to ignore multiple modifiers. if(KeyEvent.keysym.mod & g_Config.m_InpIgnoredModifiers) { - return 0; + return KEY_UNKNOWN; } - int Scancode = g_Config.m_InpTranslatedKeys ? SDL_GetScancodeFromKey(KeyEvent.keysym.sym) : KeyEvent.keysym.scancode; + int Key = g_Config.m_InpTranslatedKeys ? SDL_GetScancodeFromKey(KeyEvent.keysym.sym) : KeyEvent.keysym.scancode; #if defined(CONF_PLATFORM_ANDROID) // Translate the Android back-button to the escape-key so it can be used to open/close the menu, close popups etc. - if(Scancode == KEY_AC_BACK) + if(Key == KEY_AC_BACK) { - Scancode = KEY_ESCAPE; + Key = KEY_ESCAPE; } #endif - return Scancode; + return Key; +} + +static int TranslateMouseButtonEventKey(const SDL_MouseButtonEvent &MouseButtonEvent) +{ + switch(MouseButtonEvent.button) + { + case SDL_BUTTON_LEFT: + return KEY_MOUSE_1; + case SDL_BUTTON_RIGHT: + return KEY_MOUSE_2; + case SDL_BUTTON_MIDDLE: + return KEY_MOUSE_3; + case SDL_BUTTON_X1: + return KEY_MOUSE_4; + case SDL_BUTTON_X2: + return KEY_MOUSE_5; + case 6: + return KEY_MOUSE_6; + case 7: + return KEY_MOUSE_7; + case 8: + return KEY_MOUSE_8; + case 9: + return KEY_MOUSE_9; + default: + return KEY_UNKNOWN; + } +} + +static int TranslateMouseWheelEventKey(const SDL_MouseWheelEvent &MouseWheelEvent) +{ + if(MouseWheelEvent.y > 0) + { + return KEY_MOUSE_WHEEL_UP; + } + else if(MouseWheelEvent.y < 0) + { + return KEY_MOUSE_WHEEL_DOWN; + } + else if(MouseWheelEvent.x > 0) + { + return KEY_MOUSE_WHEEL_RIGHT; + } + else if(MouseWheelEvent.x < 0) + { + return KEY_MOUSE_WHEEL_LEFT; + } + else + { + return KEY_UNKNOWN; + } } int CInput::Update() @@ -674,26 +698,18 @@ int CInput::Update() // keep the counter between 1..0xFFFFFFFF, 0 means not pressed m_InputCounter = (m_InputCounter % std::numeric_limits::max()) + 1; - // Ensure that we have the latest keyboard, mouse and joystick state - SDL_PumpEvents(); - - int NumKeyStates; - const Uint8 *pState = SDL_GetKeyboardState(&NumKeyStates); - if(NumKeyStates >= KEY_MOUSE_1) - NumKeyStates = KEY_MOUSE_1; - mem_copy(m_aInputState, pState, NumKeyStates); - mem_zero(m_aInputState + NumKeyStates, KEY_LAST - NumKeyStates); - - // these states must always be updated manually because they are not in the SDL_GetKeyboardState from SDL - UpdateMouseState(); - UpdateJoystickState(); - SDL_Event Event; bool IgnoreKeys = false; + + const auto &&AddKeyEventChecked = [&](int Key, int Flags) { + if(Key != KEY_UNKNOWN && !IgnoreKeys && !HasComposition()) + { + AddKeyEvent(Key, Flags); + } + }; + while(SDL_PollEvent(&Event)) { - int Scancode = 0; - int Action = IInput::FLAG_PRESS; switch(Event.type) { case SDL_SYSWMEVENT: @@ -719,11 +735,11 @@ int CInput::Update() // handle keys case SDL_KEYDOWN: - Scancode = TranslateScancode(Event.key); + AddKeyEventChecked(TranslateKeyEventKey(Event.key), IInput::FLAG_PRESS); break; + case SDL_KEYUP: - Action = IInput::FLAG_RELEASE; - Scancode = TranslateScancode(Event.key); + AddKeyEventChecked(TranslateKeyEventKey(Event.key), IInput::FLAG_RELEASE); break; // handle the joystick events @@ -749,41 +765,16 @@ int CInput::Update() break; // handle mouse buttons - case SDL_MOUSEBUTTONUP: - Action = IInput::FLAG_RELEASE; - - [[fallthrough]]; case SDL_MOUSEBUTTONDOWN: - if(Event.button.button == SDL_BUTTON_LEFT) - Scancode = KEY_MOUSE_1; - if(Event.button.button == SDL_BUTTON_RIGHT) - Scancode = KEY_MOUSE_2; - if(Event.button.button == SDL_BUTTON_MIDDLE) - Scancode = KEY_MOUSE_3; - if(Event.button.button == SDL_BUTTON_X1) - Scancode = KEY_MOUSE_4; - if(Event.button.button == SDL_BUTTON_X2) - Scancode = KEY_MOUSE_5; - if(Event.button.button == 6) - Scancode = KEY_MOUSE_6; - if(Event.button.button == 7) - Scancode = KEY_MOUSE_7; - if(Event.button.button == 8) - Scancode = KEY_MOUSE_8; - if(Event.button.button == 9) - Scancode = KEY_MOUSE_9; + AddKeyEventChecked(TranslateMouseButtonEventKey(Event.button), IInput::FLAG_PRESS); + break; + + case SDL_MOUSEBUTTONUP: + AddKeyEventChecked(TranslateMouseButtonEventKey(Event.button), IInput::FLAG_RELEASE); break; case SDL_MOUSEWHEEL: - if(Event.wheel.y > 0) - Scancode = KEY_MOUSE_WHEEL_UP; - if(Event.wheel.y < 0) - Scancode = KEY_MOUSE_WHEEL_DOWN; - if(Event.wheel.x > 0) - Scancode = KEY_MOUSE_WHEEL_LEFT; - if(Event.wheel.x < 0) - Scancode = KEY_MOUSE_WHEEL_RIGHT; - Action |= IInput::FLAG_RELEASE; + AddKeyEventChecked(TranslateMouseWheelEventKey(Event.wheel), IInput::FLAG_PRESS | IInput::FLAG_RELEASE); break; case SDL_FINGERDOWN: @@ -858,16 +849,6 @@ int CInput::Update() SDL_free(Event.drop.file); break; } - - if(Scancode > KEY_FIRST && Scancode < g_MaxKeys && !IgnoreKeys && !HasComposition()) - { - if(Action & IInput::FLAG_PRESS) - { - m_aInputState[Scancode] = 1; - m_aInputCount[Scancode] = m_InputCounter; - } - AddKeyEvent(Scancode, Action); - } } return 0; diff --git a/src/engine/client/input.h b/src/engine/client/input.h index 7d491571e68..e240938b5e1 100644 --- a/src/engine/client/input.h +++ b/src/engine/client/input.h @@ -93,13 +93,11 @@ class CInput : public IEngineInput void AddTextEvent(const char *pText); // quick access to input - uint32_t m_aInputCount[g_MaxKeys]; - unsigned char m_aInputState[g_MaxKeys]; + bool m_aCurrentKeyStates[KEY_LAST]; + bool m_aFrameKeyStates[KEY_LAST]; uint32_t m_InputCounter; std::vector m_vTouchFingerStates; - void UpdateMouseState(); - void UpdateJoystickState(); void HandleJoystickAxisMotionEvent(const SDL_JoyAxisEvent &Event); void HandleJoystickButtonEvent(const SDL_JoyButtonEvent &Event); void HandleJoystickHatMotionEvent(const SDL_JoyHatEvent &Event); @@ -112,8 +110,6 @@ class CInput : public IEngineInput char m_aDropFile[IO_MAX_PATH_LENGTH]; - bool KeyState(int Key) const; - void ProcessSystemMessage(SDL_SysWMmsg *pMsg); public: @@ -127,11 +123,12 @@ class CInput : public IEngineInput void Clear() override; float GetUpdateTime() const override; - bool ModifierIsPressed() const override { return KeyState(KEY_LCTRL) || KeyState(KEY_RCTRL) || KeyState(KEY_LGUI) || KeyState(KEY_RGUI); } - bool ShiftIsPressed() const override { return KeyState(KEY_LSHIFT) || KeyState(KEY_RSHIFT); } - bool AltIsPressed() const override { return KeyState(KEY_LALT) || KeyState(KEY_RALT); } - bool KeyIsPressed(int Key) const override { return KeyState(Key); } - bool KeyPress(int Key, bool CheckCounter) const override { return CheckCounter ? (m_aInputCount[Key] == m_InputCounter) : m_aInputCount[Key]; } + bool ModifierIsPressed() const override { return KeyIsPressed(KEY_LCTRL) || KeyIsPressed(KEY_RCTRL) || KeyIsPressed(KEY_LGUI) || KeyIsPressed(KEY_RGUI); } + bool ShiftIsPressed() const override { return KeyIsPressed(KEY_LSHIFT) || KeyIsPressed(KEY_RSHIFT); } + bool AltIsPressed() const override { return KeyIsPressed(KEY_LALT) || KeyIsPressed(KEY_RALT); } + bool KeyIsPressed(int Key) const override; + bool KeyPress(int Key) const override; + const char *KeyName(int Key) const override; int FindKeyByName(const char *pKeyName) const override; size_t NumJoysticks() const override { return m_vJoysticks.size(); } diff --git a/src/engine/input.h b/src/engine/input.h index 873423b8085..86eb20d6552 100644 --- a/src/engine/input.h +++ b/src/engine/input.h @@ -14,9 +14,6 @@ #include #include -const int g_MaxKeys = 512; -extern const char g_aaKeyStrings[g_MaxKeys][20]; - class IInput : public IInterface { MACRO_INTERFACE("input") @@ -45,6 +42,9 @@ class IInput : public IInterface // events virtual void ConsumeEvents(std::function Consumer) const = 0; + /** + * Clears the events and @link KeyPress @endlink state for this frame. Must be called at the end of each frame. + */ virtual void Clear() = 0; /** @@ -57,9 +57,31 @@ class IInput : public IInterface virtual bool ModifierIsPressed() const = 0; virtual bool ShiftIsPressed() const = 0; virtual bool AltIsPressed() const = 0; + /** + * Returns whether the given key is currently pressed down. This directly represents the state of pressed keys + * based on all handled input events. + * + * This function should be used to trigger behavior continuously while a specific key is held down, e.g. for + * showing a list of all keys that are currently being pressed. + * + * @param Key The key code (see `keys.h`). + * + * @return `true` if key is currently pressed down, `false` otherwise. + */ virtual bool KeyIsPressed(int Key) const = 0; - virtual bool KeyPress(int Key, bool CheckCounter = false) const = 0; - const char *KeyName(int Key) const { return (Key >= 0 && Key < g_MaxKeys) ? g_aaKeyStrings[Key] : g_aaKeyStrings[0]; } + /** + * Returns whether the given key was pressed down during input updates for current frame. This state is + * cleared at the end of each frame by calling the @link Clear @endlink function. + * + * This function should be used to trigger behavior only once per key press event per frame, e.g. for menu + * hotkeys that should activate behavior once per key press. + * + * @param Key The key code (see `keys.h`). + * + * @return `true` if key was pressed down during input updates for the current frame, `false` otherwise. + */ + virtual bool KeyPress(int Key) const = 0; + virtual const char *KeyName(int Key) const = 0; virtual int FindKeyByName(const char *pKeyName) const = 0; // joystick