From 7558bc71ba42f681d624670d548ec3904863b166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 5 Jun 2025 18:51:16 +0200 Subject: [PATCH] Platform: add focusEvent() and blurEvent() to Application implementations. Android, Emscripten, Sdl2 and Glfw. Not to XEgl / Glx, nobody cares about those anymore. --- doc/changelog.dox | 4 ++ src/Magnum/Platform/AndroidApplication.cpp | 11 +++- src/Magnum/Platform/AndroidApplication.h | 46 +++++++++++++++ src/Magnum/Platform/EmscriptenApplication.cpp | 13 +++++ src/Magnum/Platform/EmscriptenApplication.h | 53 +++++++++++++++++ src/Magnum/Platform/GlfwApplication.cpp | 8 +++ src/Magnum/Platform/GlfwApplication.h | 33 +++++++++++ src/Magnum/Platform/Sdl2Application.cpp | 7 +++ src/Magnum/Platform/Sdl2Application.h | 57 +++++++++++++++++++ .../Platform/Test/AndroidApplicationTest.cpp | 7 +++ .../Test/EmscriptenApplicationTest.cpp | 7 +++ .../Platform/Test/GlfwApplicationTest.cpp | 7 +++ .../Platform/Test/Sdl2ApplicationTest.cpp | 7 +++ 13 files changed, 257 insertions(+), 3 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index d3586e2de..624e1fb99 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -378,6 +378,10 @@ See also: building upon [mosra/magnum#527](https://github.com/mosra/magnum/pull/527). There's also a new @ref Platform::TwoFingerGesture helper for recognition of common two-finger gestures for zoom, rotation and pan. +- Added @relativeref{Platform::Sdl2Application,focusEvent()} and + @relativeref{Platform::Sdl2Application,blurEvent()} to + @ref Platform::Sdl2Application, @ref Platform::GlfwApplication, + @ref Platform::EmscriptenApplication and @ref Platform::AndroidApplication @subsubsection changelog-latest-new-scenegraph SceneGraph library diff --git a/src/Magnum/Platform/AndroidApplication.cpp b/src/Magnum/Platform/AndroidApplication.cpp index 7add5bed9..51eb9e54c 100644 --- a/src/Magnum/Platform/AndroidApplication.cpp +++ b/src/Magnum/Platform/AndroidApplication.cpp @@ -191,6 +191,8 @@ void AndroidApplication::redraw() { } void AndroidApplication::viewportEvent(ViewportEvent&) {} +void AndroidApplication::focusEvent(FocusEvent&) {} +void AndroidApplication::blurEvent(FocusEvent&) {} namespace { struct Data { @@ -225,9 +227,12 @@ void AndroidApplication::commandEvent(android_app* state, int32_t cmd) { break; case APP_CMD_GAINED_FOCUS: - case APP_CMD_LOST_FOCUS: - /** @todo Make use of these */ - break; + case APP_CMD_LOST_FOCUS: { + FocusEvent e; + cmd == APP_CMD_GAINED_FOCUS ? + data.instance->focusEvent(e) : + data.instance->blurEvent(e); + } break; case APP_CMD_CONFIG_CHANGED: { /* This says "the current device configuration has changed", which diff --git a/src/Magnum/Platform/AndroidApplication.h b/src/Magnum/Platform/AndroidApplication.h index 75cd494ef..3e1add547 100644 --- a/src/Magnum/Platform/AndroidApplication.h +++ b/src/Magnum/Platform/AndroidApplication.h @@ -206,6 +206,7 @@ class AndroidApplication { class Configuration; class GLConfiguration; class ViewportEvent; + class FocusEvent; class InputEvent; class PointerEvent; class PointerMoveEvent; @@ -431,6 +432,25 @@ class AndroidApplication { */ virtual void viewportEvent(ViewportEvent& event); + /** + * @brief Application focus event + * @m_since_latest + * + * Called when the application gains focus. The default implementation + * does nothing. + */ + virtual void focusEvent(FocusEvent& event); + + /** + * @brief Application blur event + * @m_since_latest + * + * Called when the application loses focus, which may be happening for + * example together with the application getting suspended to + * background. The default implementation does nothing. + */ + virtual void blurEvent(FocusEvent& event); + /** @copydoc Sdl2Application::drawEvent() */ virtual void drawEvent() = 0; @@ -949,6 +969,32 @@ class AndroidApplication::ViewportEvent { const Vector2i _windowSize; }; +/** +@brief Window focus and blur event +@m_since_latest + +@see @ref focusEvent(), @ref blurEvent() +*/ +class AndroidApplication::FocusEvent { + public: + /** @brief Copying is not allowed */ + FocusEvent(const FocusEvent&) = delete; + + /** @brief Moving is not allowed */ + FocusEvent(FocusEvent&&) = delete; + + /** @brief Copying is not allowed */ + FocusEvent& operator=(const FocusEvent&) = delete; + + /** @brief Moving is not allowed */ + FocusEvent& operator=(FocusEvent&&) = delete; + + private: + friend AndroidApplication; + + explicit FocusEvent() = default; +}; + /** @brief Base for input events diff --git a/src/Magnum/Platform/EmscriptenApplication.cpp b/src/Magnum/Platform/EmscriptenApplication.cpp index f03c52bc2..89d6c317c 100644 --- a/src/Magnum/Platform/EmscriptenApplication.cpp +++ b/src/Magnum/Platform/EmscriptenApplication.cpp @@ -627,6 +627,17 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { emscripten_set_resize_callback(target, this, false, cb); } + emscripten_set_focus_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenFocusEvent* event, void* userData) -> EM_BOOL { + FocusEvent e{*event}; + static_cast(userData)->focusEvent(e); + return true; + })); + emscripten_set_blur_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenFocusEvent* event, void* userData) -> EM_BOOL { + FocusEvent e{*event}; + static_cast(userData)->blurEvent(e); + return true; + })); + /* Done this way instead of passing the lambda inline so it can have the #if inside. Because, apparently, emscripten_set_mousedown_callback() is some crazy macro, and I get "warning: embedding a directive within macro @@ -1040,6 +1051,8 @@ void EmscriptenApplication::setTextInputRect(const Range2Di&) { } void EmscriptenApplication::viewportEvent(ViewportEvent&) {} +void EmscriptenApplication::focusEvent(FocusEvent&) {} +void EmscriptenApplication::blurEvent(FocusEvent&) {} void EmscriptenApplication::keyPressEvent(KeyEvent&) {} void EmscriptenApplication::keyReleaseEvent(KeyEvent&) {} diff --git a/src/Magnum/Platform/EmscriptenApplication.h b/src/Magnum/Platform/EmscriptenApplication.h index 4fa8edae4..f47af5ac1 100644 --- a/src/Magnum/Platform/EmscriptenApplication.h +++ b/src/Magnum/Platform/EmscriptenApplication.h @@ -69,6 +69,7 @@ #endif #ifndef DOXYGEN_GENERATING_OUTPUT +struct EmscriptenFocusEvent; struct EmscriptenKeyboardEvent; struct EmscriptenMouseEvent; struct EmscriptenTouchEvent; @@ -334,6 +335,7 @@ class EmscriptenApplication { class Configuration; class GLConfiguration; class ViewportEvent; + class FocusEvent; class InputEvent; class PointerEvent; class PointerMoveEvent; @@ -668,6 +670,26 @@ class EmscriptenApplication { */ virtual void viewportEvent(ViewportEvent& event); + /** + * @brief Canvas focus event + * @m_since_latest + * + * Called when the canvas gains (keyboard) input focus. The default + * implementation does nothing. + */ + virtual void focusEvent(FocusEvent& event); + + /** + * @brief Canvas blur event + * @m_since_latest + * + * Called when the canvas loses (keyboard) input focus, which for + * example can be used by the application code to discard all + * information about currently pressed keys. The default implementation + * does nothing. + */ + virtual void blurEvent(FocusEvent& event); + /** @copydoc Sdl2Application::drawEvent() */ virtual void drawEvent() = 0; @@ -2000,6 +2022,37 @@ class EmscriptenApplication::ViewportEvent { const Vector2 _dpiScaling, _devicePixelRatio; }; +/** +@brief Window focus and blur event +@m_since_latest + +@see @ref focusEvent(), @ref blurEvent() +*/ +class EmscriptenApplication::FocusEvent { + public: + /** @brief Copying is not allowed */ + FocusEvent(const FocusEvent&) = delete; + + /** @brief Moving is not allowed */ + FocusEvent(FocusEvent&&) = delete; + + /** @brief Copying is not allowed */ + FocusEvent& operator=(const FocusEvent&) = delete; + + /** @brief Moving is not allowed */ + FocusEvent& operator=(FocusEvent&&) = delete; + + /** @brief Underlying Emscripten event */ + const EmscriptenFocusEvent& event() const { return _event; } + + private: + friend EmscriptenApplication; + + explicit FocusEvent(const EmscriptenFocusEvent& event): _event(event) {} + + const EmscriptenFocusEvent& _event; +}; + /** @brief Base for input events diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index 141d8ec29..afb17e413 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -670,6 +670,12 @@ void GlfwApplication::setupCallbacks() { #endif app.viewportEvent(e); }); + glfwSetWindowFocusCallback(_window, [](GLFWwindow* const window, const int focused) { + auto& app = *static_cast(glfwGetWindowUserPointer(window)); + + FocusEvent e; + focused ? app.focusEvent(e) : app.blurEvent(e); + }); glfwSetKeyCallback(_window, [](GLFWwindow* const window, const int key, const int scancode, const int action, const int mods) { auto& app = *static_cast(glfwGetWindowUserPointer(window)); @@ -974,6 +980,8 @@ void GlfwApplication::tickEvent() { } void GlfwApplication::viewportEvent(ViewportEvent&) {} +void GlfwApplication::focusEvent(FocusEvent&) {} +void GlfwApplication::blurEvent(FocusEvent&) {} void GlfwApplication::keyPressEvent(KeyEvent&) {} void GlfwApplication::keyReleaseEvent(KeyEvent&) {} diff --git a/src/Magnum/Platform/GlfwApplication.h b/src/Magnum/Platform/GlfwApplication.h index e618ee0ad..256d1fa6d 100644 --- a/src/Magnum/Platform/GlfwApplication.h +++ b/src/Magnum/Platform/GlfwApplication.h @@ -176,6 +176,7 @@ class GlfwApplication { #endif class ExitEvent; class ViewportEvent; + class FocusEvent; class InputEvent; class KeyEvent; class PointerEvent; @@ -638,6 +639,12 @@ class GlfwApplication { */ virtual void viewportEvent(ViewportEvent& event); + /** @copydoc Sdl2Application::focusEvent() */ + virtual void focusEvent(FocusEvent& event); + + /** @copydoc Sdl2Application::blurEvent() */ + virtual void blurEvent(FocusEvent& event); + /** @copydoc Sdl2Application::drawEvent() */ virtual void drawEvent() = 0; @@ -2112,6 +2119,32 @@ class GlfwApplication::ViewportEvent { const Vector2 _dpiScaling; }; +/** +@brief Window focus and blur event +@m_since_latest + +@see @ref focusEvent(), @ref blurEvent() +*/ +class GlfwApplication::FocusEvent { + public: + /** @brief Copying is not allowed */ + FocusEvent(const FocusEvent&) = delete; + + /** @brief Moving is not allowed */ + FocusEvent(FocusEvent&&) = delete; + + /** @brief Copying is not allowed */ + FocusEvent& operator=(const FocusEvent&) = delete; + + /** @brief Moving is not allowed */ + FocusEvent& operator=(FocusEvent&&) = delete; + + private: + friend GlfwApplication; + + explicit FocusEvent() = default; +}; + /** @brief Base for input events diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index c4581a714..7c4214de3 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -1064,6 +1064,11 @@ bool Sdl2Application::mainLoopIteration() { _flags |= Flag::Redraw; #endif } break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_FOCUS_LOST: { + FocusEvent e{event}; + event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED ? focusEvent(e) : blurEvent(e); + } break; /* Direct everything that wasn't exposed via a callback to anyEvent(), so users can implement event handling for things not present in the Application APIs */ @@ -1466,6 +1471,8 @@ void Sdl2Application::anyEvent(SDL_Event&) { } void Sdl2Application::viewportEvent(ViewportEvent&) {} +void Sdl2Application::focusEvent(FocusEvent&) {} +void Sdl2Application::blurEvent(FocusEvent&) {} void Sdl2Application::keyPressEvent(KeyEvent&) {} void Sdl2Application::keyReleaseEvent(KeyEvent&) {} diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index 4145fc710..87276554c 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -551,6 +551,7 @@ class Sdl2Application { #endif class ExitEvent; class ViewportEvent; + class FocusEvent; class InputEvent; class KeyEvent; class PointerEvent; @@ -1113,6 +1114,26 @@ class Sdl2Application { */ virtual void viewportEvent(ViewportEvent& event); + /** + * @brief Window focus event + * @m_since_latest + * + * Called when the window gains (keyboard) input focus. The default + * implementation does nothing. + */ + virtual void focusEvent(FocusEvent& event); + + /** + * @brief Window blur event + * @m_since_latest + * + * Called when the window loses (keyboard) input focus, which for + * example can be used by the application code to discard all + * information about currently pressed keys. The default implementation + * does nothing. + */ + virtual void blurEvent(FocusEvent& event); + /** * @brief Draw event * @@ -2830,6 +2851,42 @@ class Sdl2Application::ViewportEvent { const Vector2 _dpiScaling; }; +/** +@brief Window focus and blur event +@m_since_latest + +@see @ref focusEvent(), @ref blurEvent() +*/ +class Sdl2Application::FocusEvent { + public: + /** @brief Copying is not allowed */ + FocusEvent(const FocusEvent&) = delete; + + /** @brief Moving is not allowed */ + FocusEvent(FocusEvent&&) = delete; + + /** @brief Copying is not allowed */ + FocusEvent& operator=(const FocusEvent&) = delete; + + /** @brief Moving is not allowed */ + FocusEvent& operator=(FocusEvent&&) = delete; + + /** + * @brief Underlying SDL event + * + * Of type `SDL_WINDOWEVENT` + * @see @ref Sdl2Application::anyEvent() + */ + const SDL_Event& event() const { return _event; } + + private: + friend Sdl2Application; + + explicit FocusEvent(const SDL_Event& event): _event(event) {} + + const SDL_Event& _event; +}; + /** @brief Base for input events diff --git a/src/Magnum/Platform/Test/AndroidApplicationTest.cpp b/src/Magnum/Platform/Test/AndroidApplicationTest.cpp index 78156a950..2c609d7d7 100644 --- a/src/Magnum/Platform/Test/AndroidApplicationTest.cpp +++ b/src/Magnum/Platform/Test/AndroidApplicationTest.cpp @@ -142,6 +142,13 @@ struct AndroidApplicationTest: Platform::Application { Debug{} << "viewport:" << event.windowSize() << event.framebufferSize() << event.dpiScaling(); } + void focusEvent(FocusEvent&) override { + Debug{} << "application focused"; + } + void blurEvent(FocusEvent&) override { + Debug{} << "application blurred"; + } + /* Set to 0 to test the deprecated mouse events instead */ #if 1 void pointerPressEvent(PointerEvent& event) override { diff --git a/src/Magnum/Platform/Test/EmscriptenApplicationTest.cpp b/src/Magnum/Platform/Test/EmscriptenApplicationTest.cpp index dc74c2f9c..1897465b3 100644 --- a/src/Magnum/Platform/Test/EmscriptenApplicationTest.cpp +++ b/src/Magnum/Platform/Test/EmscriptenApplicationTest.cpp @@ -314,6 +314,13 @@ struct EmscriptenApplicationTest: Platform::Application { } #endif + void focusEvent(FocusEvent&) override { + Debug{} << "canvas focused"; + } + void blurEvent(FocusEvent&) override { + Debug{} << "canvas blurred"; + } + /* Set to 0 to test the deprecated mouse events instead */ #if 1 void pointerPressEvent(PointerEvent& event) override { diff --git a/src/Magnum/Platform/Test/GlfwApplicationTest.cpp b/src/Magnum/Platform/Test/GlfwApplicationTest.cpp index bc0198bfc..b78ae7bc7 100644 --- a/src/Magnum/Platform/Test/GlfwApplicationTest.cpp +++ b/src/Magnum/Platform/Test/GlfwApplicationTest.cpp @@ -285,6 +285,13 @@ struct GlfwApplicationTest: Platform::Application { << event.dpiScaling(); } + void focusEvent(FocusEvent&) override { + Debug{} << "window focused"; + } + void blurEvent(FocusEvent&) override { + Debug{} << "window blurred"; + } + void exitEvent(ExitEvent& event) override { Debug{} << "application exiting"; event.setAccepted(); /* Comment-out to test app exit suppression */ diff --git a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp index 0c686157b..abe3aefd4 100644 --- a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp +++ b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp @@ -328,6 +328,13 @@ struct Sdl2ApplicationTest: Platform::Application { << event.dpiScaling(); } + void focusEvent(FocusEvent&) override { + Debug{} << "window focused"; + } + void blurEvent(FocusEvent&) override { + Debug{} << "window blurred"; + } + void drawEvent() override { Debug{} << "draw event"; #ifdef MAGNUM_TARGET_GL