diff --git a/include/SFML/Window/Event.hpp b/include/SFML/Window/Event.hpp index 947b84420..9c4dfd982 100644 --- a/include/SFML/Window/Event.hpp +++ b/include/SFML/Window/Event.hpp @@ -299,6 +299,17 @@ public: template [[nodiscard]] const TEventSubtype* getIf() const; + //////////////////////////////////////////////////////////// + /// \brief Apply a visitor to the event + /// + /// \param visitor The visitor to apply + /// + /// \return The result of applying the visitor to the event + /// + //////////////////////////////////////////////////////////// + template + decltype(auto) visit(T&& visitor) const; + private: //////////////////////////////////////////////////////////// // Member data diff --git a/include/SFML/Window/Event.inl b/include/SFML/Window/Event.inl index faa32aee2..70cb639c1 100644 --- a/include/SFML/Window/Event.inl +++ b/include/SFML/Window/Event.inl @@ -67,4 +67,12 @@ const TEventSubtype* Event::getIf() const return std::get_if(&m_data); } + +//////////////////////////////////////////////////////////// +template +decltype(auto) Event::visit(T&& visitor) const +{ + return std::visit(std::forward(visitor), m_data); +} + } // namespace sf diff --git a/include/SFML/Window/WindowBase.hpp b/include/SFML/Window/WindowBase.hpp index 0b6ec38ab..729a2924d 100644 --- a/include/SFML/Window/WindowBase.hpp +++ b/include/SFML/Window/WindowBase.hpp @@ -206,7 +206,7 @@ public: /// /// \return The event, otherwise `std::nullopt` if no events are pending /// - /// \see waitEvent + /// \see waitEvent, handleEvents /// //////////////////////////////////////////////////////////// [[nodiscard]] std::optional pollEvent(); @@ -232,11 +232,89 @@ public: /// /// \return The event, otherwise `std::nullopt` on timeout or if window was closed /// - /// \see pollEvent + /// \see pollEvent, handleEvents /// //////////////////////////////////////////////////////////// [[nodiscard]] std::optional waitEvent(Time timeout = Time::Zero); + //////////////////////////////////////////////////////////// + /// \brief Handle all pending events + /// + /// This function is not blocking: if there's no pending event then + /// it will return without calling any of the handlers. + /// + /// This function can take a variadic list of event handlers that + /// each take a concrete event type as a single parameter. The event + /// handlers can be any kind of callable object that has an + /// operator() defined for a specific event type. Additionally a + /// generic callable can also be provided that will be invoked for + /// every event type. If both types of callables are provided, the + /// callables taking concrete event types will be prefered over the + /// generic callable by overload resolution. Generic callables can + /// be used to customize handler dispatching based on the deduced + /// type of the event and other information available at compile + /// time. + /// + /// Examples of callables: + /// - Lambda expressions: `[&](const sf::Event::KeyPressed) { ... }` + /// - Free functions: `void handler(const sf::Event::KeyPressed&) { ... }` + /// + /// \code + /// // Only provide handlers for concrete event types + /// window.handleEvents( + /// [&](const sf::Event::Closed&) { /* handle event */ }, + /// [&](const sf::Event::KeyPressed& keyPress) { /* handle event */ } + /// ); + /// \endcode + /// \code + /// // Provide a generic event handler + /// window.handleEvents( + /// [&](const auto& event) + /// { + /// if constexpr (std::is_same_v, sf::Event::Closed>) + /// { + /// // Handle Closed + /// handleClosed(); + /// } + /// else if constexpr (std::is_same_v, sf::Event::KeyPressed>) + /// { + /// // Handle KeyPressed + /// handleKeyPressed(event); + /// } + /// else + /// { + /// // Handle non-KeyPressed + /// handleOtherEvents(event); + /// } + /// } + /// ); + /// \endcode + /// \code + /// // Provide handlers for concrete types and fall back to generic handler + /// window.handleEvents( + /// [&](const sf::Event::Closed&) { /* handle event */ }, + /// [&](const sf::Event::KeyPressed& keyPress) { /* handle event */ }, + /// [&](const auto& event) { /* handle all other events */ } + /// ); + /// \endcode + /// + /// Calling member functions is supported through lambda + /// expressions. + /// \code + /// // Provide a generic event handler + /// window.handleEvents( + /// [this](const auto& event) { handle(event); } + /// ); + /// \endcode + /// + /// \param handlers A variadic list of callables that take a specific event as their only parameter + /// + /// \see waitEvent, pollEvent + /// + //////////////////////////////////////////////////////////// + template + void handleEvents(Ts&&... handlers); + //////////////////////////////////////////////////////////// /// \brief Get the position of the window /// @@ -520,6 +598,7 @@ private: } // namespace sf +#include //////////////////////////////////////////////////////////// /// \class sf::WindowBase diff --git a/include/SFML/Window/WindowBase.inl b/include/SFML/Window/WindowBase.inl new file mode 100644 index 000000000..52b25791c --- /dev/null +++ b/include/SFML/Window/WindowBase.inl @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2024 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include // NOLINT(misc-header-include-cycle) + +#include + + +namespace sf +{ +namespace priv +{ +template +struct OverloadSet : Ts... +{ + using Ts::operator()...; +}; +template +OverloadSet(Ts...) -> OverloadSet; + +template +struct OverloadSetWithDefault : OverloadSet +{ + // By providing our own operator() and forwarding based + // on invocability of OverloadSet on the concrete type + // of the value, we save the user from having to provide + // their own catch-all overload if they don't want to + template + void operator()([[maybe_unused]] T&& value) // NOLINT(cppcoreguidelines-missing-std-forward) + { + if constexpr (std::is_invocable_v, T>) + OverloadSet::operator()(std::forward(value)); + } +}; +template +OverloadSetWithDefault(Ts...) -> OverloadSetWithDefault; +} // namespace priv + + +//////////////////////////////////////////////////////////// +template +void WindowBase::handleEvents(Ts&&... handlers) // NOLINT(cppcoreguidelines-missing-std-forward) +{ + // Disable misc-const-correctness for this line since clang-tidy + // complains about it even though the code would become uncompilable + priv::OverloadSetWithDefault overloadSet{std::forward(handlers)...}; // NOLINT(misc-const-correctness) + + while (const std::optional event = pollEvent()) + event->visit(overloadSet); +} + +} // namespace sf diff --git a/src/SFML/Window/CMakeLists.txt b/src/SFML/Window/CMakeLists.txt index ab59e10bb..c311e37bc 100644 --- a/src/SFML/Window/CMakeLists.txt +++ b/src/SFML/Window/CMakeLists.txt @@ -46,6 +46,7 @@ set(SRC ${INCROOT}/Window.hpp ${SRCROOT}/WindowBase.cpp ${INCROOT}/WindowBase.hpp + ${INCROOT}/WindowBase.inl ${INCROOT}/WindowEnums.hpp ${INCROOT}/WindowHandle.hpp ${SRCROOT}/WindowImpl.cpp diff --git a/test/Window/Event.test.cpp b/test/Window/Event.test.cpp index 6fa25bd57..198c60c9d 100644 --- a/test/Window/Event.test.cpp +++ b/test/Window/Event.test.cpp @@ -2,8 +2,36 @@ #include +#include #include +namespace +{ +struct +{ + std::string_view operator()(const sf::Event::Closed&) const + { + return "Closed"; + } + + std::string_view operator()(const sf::Event::Resized&) const + { + return "Resized"; + } + + std::string_view operator()(const sf::Event::KeyPressed&) const + { + return "KeyPressed"; + } + + template + std::string_view operator()(const T&) const + { + return "Other"; + } +} visitor; +} // namespace + TEST_CASE("[Window] sf::Event") { SECTION("Type traits") @@ -266,4 +294,13 @@ TEST_CASE("[Window] sf::Event") CHECK(sensorChanged.type == sf::Sensor::Type{}); CHECK(sensorChanged.value == sf::Vector3f()); } + + SECTION("visit()") + { + CHECK(sf::Event(sf::Event::Closed{}).visit(visitor) == "Closed"); + CHECK(sf::Event(sf::Event::Resized{}).visit(visitor) == "Resized"); + CHECK(sf::Event(sf::Event::FocusLost{}).visit(visitor) == "Other"); + CHECK(sf::Event(sf::Event::FocusGained{}).visit(visitor) == "Other"); + CHECK(sf::Event(sf::Event::KeyPressed{}).visit(visitor) == "KeyPressed"); + } }