From 70b0d76fcbb5d6d0fe43b3119446b0045fef64e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 4 May 2025 18:36:28 +0200 Subject: [PATCH] Platform: add {Sdl2,Glfw}Application::isKeyPressed(). And also immediately document it's not recommended to be used if it's important to not miss any events, so basically next to useless. Yet some projects implement their own key state caching on top of Platform::Application, so it's better to give them the builtin thing than suffer needless wheel reinvention. --- doc/changelog.dox | 3 + src/Magnum/Platform/GlfwApplication.cpp | 7 + src/Magnum/Platform/GlfwApplication.h | 26 ++- src/Magnum/Platform/Sdl2Application.cpp | 10 + src/Magnum/Platform/Sdl2Application.h | 29 ++- .../Platform/Test/GlfwApplicationTest.cpp | 197 +++++++++--------- .../Platform/Test/Sdl2ApplicationTest.cpp | 11 + 7 files changed, 182 insertions(+), 101 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 3f5df63cf..11b4471f0 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -882,6 +882,9 @@ See also: @relativeref{Platform::Sdl2Application,scanCodeToKey()} and @ref Platform::GlfwApplication::keyToScanCode() helpers for key / scan code conversion outside of event handlers +- Added @ref Platform::Sdl2Application::isKeyPressed() and + @ref Platform::GlfwApplication::isKeyPressed() for immediate key state + queries - Updated @ref Platform::AndroidApplication to not use a deprecated API that was removed in NDK 27 ([mosra/magnum#659](https://github.com/mosra/magnum/pull/659)) diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index 4e148b2ac..141d8ec29 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -905,6 +905,13 @@ void GlfwApplication::exit(int exitCode) { if(_window) glfwSetWindowShouldClose(_window, true); } +bool GlfwApplication::isKeyPressed(const Key key) { + /* Documentation says GLFW_KEY_UNKNOWN is not valid for glfwGetKey() */ + if(key == Key::Unknown) + return false; + return glfwGetKey(_window, int(key)) == GLFW_PRESS; +} + namespace { constexpr Int CursorMap[] { diff --git a/src/Magnum/Platform/GlfwApplication.h b/src/Magnum/Platform/GlfwApplication.h index 24341b54d..a7310cb32 100644 --- a/src/Magnum/Platform/GlfwApplication.h +++ b/src/Magnum/Platform/GlfwApplication.h @@ -649,11 +649,30 @@ class GlfwApplication { /** @{ @name Keyboard handling */ + public: + /** + * @brief Whether a key is pressed + * @m_since_latest + * + * If a key is pressed, i.e. @ref keyPressEvent() with given @p key was + * fired but not a @ref keyReleaseEvent() yet, the function returns + * @cpp true @ce. For unknown @ref Key values returns @cpp false @ce. + * + * This function only queries immediate keyboard state at given point + * in time, which means that if a particular key got pressed and + * released again in between calls to this function, it will not be + * reported as pressed. To avoid losing key presses, prefer to get + * keyboard press and release events through @ref keyPressEvent() and + * @ref keyReleaseEvent() instead if possible. + */ + bool isKeyPressed(Key key); + + private: /** * @brief Key press event * * Called when a key is pressed. Default implementation does nothing. - * @see @ref platform-windowed-key-events + * @see @ref platform-windowed-key-events, @ref isKeyPressed() */ virtual void keyPressEvent(KeyEvent& event); @@ -661,7 +680,7 @@ class GlfwApplication { * @brief Key release event * * Called when a key is released. Default implementation does nothing. - * @see @ref platform-windowed-key-events + * @see @ref platform-windowed-key-events, @ref isKeyPressed() */ virtual void keyReleaseEvent(KeyEvent& event); @@ -1061,7 +1080,8 @@ CORRADE_ENUMSET_OPERATORS(GlfwApplication::Modifiers) @brief Key @m_since_latest -@see @ref KeyEvent::key(), @ref platform-windowed-key-events +@see @ref KeyEvent::key(), @ref isKeyPressed(), @ref keyToScanCode(), + @ref platform-windowed-key-events */ enum class GlfwApplication::Key: Int { Unknown = GLFW_KEY_UNKNOWN, /**< Unknown key */ diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index 3adc63f03..c4581a714 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -1306,6 +1306,16 @@ bool Sdl2Application::mainLoopIteration() { return !(_flags & Flag::Exit); } +#ifndef CORRADE_TARGET_EMSCRIPTEN +bool Sdl2Application::isKeyPressed(const Key key) { + int count; + const Uint8* const state = SDL_GetKeyboardState(&count); + const SDL_Scancode scancode = SDL_GetScancodeFromKey(SDL_Keycode(key)); + CORRADE_INTERNAL_DEBUG_ASSERT(scancode < count); + return state[scancode]; +} +#endif + namespace { #ifndef CORRADE_TARGET_EMSCRIPTEN diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index f11123f90..bb2eb7d4b 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -1132,11 +1132,33 @@ class Sdl2Application { /** @{ @name Keyboard handling */ + public: + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Whether a key is pressed + * @m_since_latest + * + * If a key is pressed, i.e. @ref keyPressEvent() with given @p key was + * fired but not a @ref keyReleaseEvent() yet, the function returns + * @cpp true @ce. For unknown @ref Key values returns @cpp false @ce. + * + * This function only queries immediate keyboard state at given point + * in time, which means that if a particular key got pressed and + * released again in between calls to this function, it will not be + * reported as pressed. To avoid losing key presses, prefer to get + * keyboard press and release events through @ref keyPressEvent() and + * @ref keyReleaseEvent() instead if possible. + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + bool isKeyPressed(Key key); + #endif + + private: /** * @brief Key press event * * Called when a key is pressed. Default implementation does nothing. - * @see @ref platform-windowed-key-events + * @see @ref platform-windowed-key-events, @ref isKeyPressed() */ virtual void keyPressEvent(KeyEvent& event); @@ -1144,7 +1166,7 @@ class Sdl2Application { * @brief Key release event * * Called when a key is released. Default implementation does nothing. - * @see @ref platform-windowed-key-events + * @see @ref platform-windowed-key-events, @ref isKeyPressed() */ virtual void keyReleaseEvent(KeyEvent& event); @@ -1632,7 +1654,8 @@ CORRADE_ENUMSET_OPERATORS(Sdl2Application::Modifiers) @brief Key @m_since_latest -@see @ref KeyEvent::key(), @ref platform-windowed-key-events +@see @ref KeyEvent::key(), @ref isKeyPressed(), @ref keyToScanCode(), + @ref scanCodeToKey(), @ref platform-windowed-key-events */ enum class Sdl2Application::Key: SDL_Keycode { Unknown = SDLK_UNKNOWN, /**< Unknown key */ diff --git a/src/Magnum/Platform/Test/GlfwApplicationTest.cpp b/src/Magnum/Platform/Test/GlfwApplicationTest.cpp index 350ecb9ad..bc0198bfc 100644 --- a/src/Magnum/Platform/Test/GlfwApplicationTest.cpp +++ b/src/Magnum/Platform/Test/GlfwApplicationTest.cpp @@ -39,8 +39,8 @@ namespace Magnum { namespace Platform { -/* These cannot be in an anonymous namespace as enumSetDebugOutput() below - wouldn't be able to pick them up */ +/* These cannot be in an anonymous namespace as enumSetDebugOutput() / Key + below wouldn't be able to pick them up */ static Debug& operator<<(Debug& debug, Application::Modifier value) { debug << "Modifier" << Debug::nospace; @@ -57,99 +57,7 @@ static Debug& operator<<(Debug& debug, Application::Modifier value) { return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; } -static Debug& operator<<(Debug& debug, Application::Pointer value) { - debug << "Pointer" << Debug::nospace; - - switch(value) { - #define _c(value) case Application::Pointer::value: return debug << "::" #value; - _c(MouseLeft) - _c(MouseMiddle) - _c(MouseRight) - _c(MouseButton4) - _c(MouseButton5) - _c(MouseButton6) - _c(MouseButton7) - _c(MouseButton8) - #undef _c - } - - return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; -} - -#ifdef MAGNUM_BUILD_DEPRECATED -CORRADE_IGNORE_DEPRECATED_PUSH -CORRADE_UNUSED static Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Button value) { - debug << "Button" << Debug::nospace; - - switch(value) { - #define _c(value) case Application::MouseMoveEvent::Button::value: return debug << "::" #value; - _c(Left) - _c(Middle) - _c(Right) - #undef _c - } - - return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; -} -CORRADE_IGNORE_DEPRECATED_POP -#endif - -namespace Test { namespace { - -Debug& operator<<(Debug& debug, Application::Modifiers value) { - return Containers::enumSetDebugOutput(debug, value, "Modifiers{}", { - Application::Modifier::Shift, - Application::Modifier::Ctrl, - Application::Modifier::Alt, - Application::Modifier::Super - }); -} - -Debug& operator<<(Debug& debug, Application::Pointers value) { - return Containers::enumSetDebugOutput(debug, value, "Pointers{}", { - Application::Pointer::MouseLeft, - Application::Pointer::MouseMiddle, - Application::Pointer::MouseRight, - Application::Pointer::MouseButton4, - Application::Pointer::MouseButton5, - Application::Pointer::MouseButton6, - Application::Pointer::MouseButton7, - Application::Pointer::MouseButton8, - }); -} - -#ifdef MAGNUM_BUILD_DEPRECATED -CORRADE_IGNORE_DEPRECATED_PUSH -CORRADE_UNUSED Debug& operator<<(Debug& debug, Application::MouseEvent::Button value) { - debug << "Button" << Debug::nospace; - - switch(value) { - #define _c(value) case Application::MouseEvent::Button::value: return debug << "::" #value; - _c(Left) - _c(Middle) - _c(Right) - _c(Button4) - _c(Button5) - _c(Button6) - _c(Button7) - _c(Button8) - #undef _c - } - - return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; -} - -CORRADE_UNUSED Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Buttons value) { - return Containers::enumSetDebugOutput(debug, value, "Buttons{}", { - Application::MouseMoveEvent::Button::Left, - Application::MouseMoveEvent::Button::Middle, - Application::MouseMoveEvent::Button::Right, - }); -} -CORRADE_IGNORE_DEPRECATED_POP -#endif - -Debug& operator<<(Debug& debug, const Application::Key value) { +static Debug& operator<<(Debug& debug, const Application::Key value) { debug << "Key" << Debug::nospace; switch(value) { @@ -270,6 +178,98 @@ Debug& operator<<(Debug& debug, const Application::Key value) { return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; } +static Debug& operator<<(Debug& debug, Application::Pointer value) { + debug << "Pointer" << Debug::nospace; + + switch(value) { + #define _c(value) case Application::Pointer::value: return debug << "::" #value; + _c(MouseLeft) + _c(MouseMiddle) + _c(MouseRight) + _c(MouseButton4) + _c(MouseButton5) + _c(MouseButton6) + _c(MouseButton7) + _c(MouseButton8) + #undef _c + } + + return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_UNUSED static Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Button value) { + debug << "Button" << Debug::nospace; + + switch(value) { + #define _c(value) case Application::MouseMoveEvent::Button::value: return debug << "::" #value; + _c(Left) + _c(Middle) + _c(Right) + #undef _c + } + + return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; +} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +namespace Test { namespace { + +Debug& operator<<(Debug& debug, Application::Modifiers value) { + return Containers::enumSetDebugOutput(debug, value, "Modifiers{}", { + Application::Modifier::Shift, + Application::Modifier::Ctrl, + Application::Modifier::Alt, + Application::Modifier::Super + }); +} + +Debug& operator<<(Debug& debug, Application::Pointers value) { + return Containers::enumSetDebugOutput(debug, value, "Pointers{}", { + Application::Pointer::MouseLeft, + Application::Pointer::MouseMiddle, + Application::Pointer::MouseRight, + Application::Pointer::MouseButton4, + Application::Pointer::MouseButton5, + Application::Pointer::MouseButton6, + Application::Pointer::MouseButton7, + Application::Pointer::MouseButton8, + }); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_UNUSED Debug& operator<<(Debug& debug, Application::MouseEvent::Button value) { + debug << "Button" << Debug::nospace; + + switch(value) { + #define _c(value) case Application::MouseEvent::Button::value: return debug << "::" #value; + _c(Left) + _c(Middle) + _c(Right) + _c(Button4) + _c(Button5) + _c(Button6) + _c(Button7) + _c(Button8) + #undef _c + } + + return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; +} + +CORRADE_UNUSED Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Buttons value) { + return Containers::enumSetDebugOutput(debug, value, "Buttons{}", { + Application::MouseMoveEvent::Button::Left, + Application::MouseMoveEvent::Button::Middle, + Application::MouseMoveEvent::Button::Right, + }); +} +CORRADE_IGNORE_DEPRECATED_POP +#endif + using namespace Containers::Literals; using namespace Math::Literals; @@ -294,6 +294,10 @@ struct GlfwApplicationTest: Platform::Application { Debug{} << "draw"; swapBuffers(); + /* Invalid keys are tested in the constructor */ + if(isKeyPressed(Key::M)) + Debug{} << Key::M << "is pressed"; + if(_redraw) redraw(); } @@ -458,6 +462,9 @@ GlfwApplicationTest::GlfwApplicationTest(const Arguments& arguments): Platform:: importer->openData(rs.getRaw("icon-32.tga")) && (image32 = importer->image2D(0)) && importer->openData(rs.getRaw("icon-64.tga")) && (image64 = importer->image2D(0))) setWindowIcon({*image16, *image32, *image64}); else Warning{} << "Can't load the plugin / images, not setting window icon"; + + /* This shouldn't blow up */ + CORRADE_INTERNAL_ASSERT(!isKeyPressed(Key::Unknown) && !isKeyPressed(Key(0x7fffffff))); } }}}} diff --git a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp index 2f4d5ff46..0c686157b 100644 --- a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp +++ b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp @@ -334,6 +334,12 @@ struct Sdl2ApplicationTest: Platform::Application { GL::defaultFramebuffer.clear(GL::FramebufferClear::Color); #endif + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* Invalid keys are tested in the constructor */ + if(isKeyPressed(Key::M)) + Debug{} << Key::M << "is pressed"; + #endif + swapBuffers(); if(_redraw) @@ -578,6 +584,11 @@ Sdl2ApplicationTest::Sdl2ApplicationTest(const Arguments& arguments): Platform:: Debug{} << "SDL too old, can't set window icon"; #endif #endif + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* This shouldn't blow up */ + CORRADE_INTERNAL_ASSERT(!isKeyPressed(Key::Unknown) && !isKeyPressed(Key(0x7fffffff))); + #endif } }}}}