Added visitation support to Event and WindowBase via handleEvents.

This commit is contained in:
binary1248 2024-06-28 12:07:25 +02:00 committed by Chris Thrasher
parent 5873a7a157
commit 41c48a84bc
6 changed files with 215 additions and 2 deletions

View File

@ -299,6 +299,17 @@ public:
template <typename TEventSubtype> template <typename TEventSubtype>
[[nodiscard]] const TEventSubtype* getIf() const; [[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 <typename T>
decltype(auto) visit(T&& visitor) const;
private: private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data

View File

@ -67,4 +67,12 @@ const TEventSubtype* Event::getIf() const
return std::get_if<TEventSubtype>(&m_data); return std::get_if<TEventSubtype>(&m_data);
} }
////////////////////////////////////////////////////////////
template <typename T>
decltype(auto) Event::visit(T&& visitor) const
{
return std::visit(std::forward<T>(visitor), m_data);
}
} // namespace sf } // namespace sf

View File

@ -206,7 +206,7 @@ public:
/// ///
/// \return The event, otherwise `std::nullopt` if no events are pending /// \return The event, otherwise `std::nullopt` if no events are pending
/// ///
/// \see waitEvent /// \see waitEvent, handleEvents
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] std::optional<Event> pollEvent(); [[nodiscard]] std::optional<Event> pollEvent();
@ -232,11 +232,89 @@ public:
/// ///
/// \return The event, otherwise `std::nullopt` on timeout or if window was closed /// \return The event, otherwise `std::nullopt` on timeout or if window was closed
/// ///
/// \see pollEvent /// \see pollEvent, handleEvents
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] std::optional<Event> waitEvent(Time timeout = Time::Zero); [[nodiscard]] std::optional<Event> 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<std::decay_t<decltype(event)>, sf::Event::Closed>)
/// {
/// // Handle Closed
/// handleClosed();
/// }
/// else if constexpr (std::is_same_v<std::decay_t<decltype(event)>, 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 <typename... Ts>
void handleEvents(Ts&&... handlers);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get the position of the window /// \brief Get the position of the window
/// ///
@ -520,6 +598,7 @@ private:
} // namespace sf } // namespace sf
#include <SFML/Window/WindowBase.inl>
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \class sf::WindowBase /// \class sf::WindowBase

View File

@ -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 <SFML/Window/Event.hpp>
#include <SFML/Window/WindowBase.hpp> // NOLINT(misc-header-include-cycle)
#include <type_traits>
namespace sf
{
namespace priv
{
template <typename... Ts>
struct OverloadSet : Ts...
{
using Ts::operator()...;
};
template <typename... Ts>
OverloadSet(Ts...) -> OverloadSet<Ts...>;
template <typename... Ts>
struct OverloadSetWithDefault : OverloadSet<Ts...>
{
// By providing our own operator() and forwarding based
// on invocability of OverloadSet<Ts...> 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 <typename T>
void operator()([[maybe_unused]] T&& value) // NOLINT(cppcoreguidelines-missing-std-forward)
{
if constexpr (std::is_invocable_v<OverloadSet<Ts...>, T>)
OverloadSet<Ts...>::operator()(std::forward<T>(value));
}
};
template <typename... Ts>
OverloadSetWithDefault(Ts...) -> OverloadSetWithDefault<Ts...>;
} // namespace priv
////////////////////////////////////////////////////////////
template <typename... Ts>
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<Ts>(handlers)...}; // NOLINT(misc-const-correctness)
while (const std::optional event = pollEvent())
event->visit(overloadSet);
}
} // namespace sf

View File

@ -46,6 +46,7 @@ set(SRC
${INCROOT}/Window.hpp ${INCROOT}/Window.hpp
${SRCROOT}/WindowBase.cpp ${SRCROOT}/WindowBase.cpp
${INCROOT}/WindowBase.hpp ${INCROOT}/WindowBase.hpp
${INCROOT}/WindowBase.inl
${INCROOT}/WindowEnums.hpp ${INCROOT}/WindowEnums.hpp
${INCROOT}/WindowHandle.hpp ${INCROOT}/WindowHandle.hpp
${SRCROOT}/WindowImpl.cpp ${SRCROOT}/WindowImpl.cpp

View File

@ -2,8 +2,36 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <string_view>
#include <type_traits> #include <type_traits>
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 <typename T>
std::string_view operator()(const T&) const
{
return "Other";
}
} visitor;
} // namespace
TEST_CASE("[Window] sf::Event") TEST_CASE("[Window] sf::Event")
{ {
SECTION("Type traits") SECTION("Type traits")
@ -266,4 +294,13 @@ TEST_CASE("[Window] sf::Event")
CHECK(sensorChanged.type == sf::Sensor::Type{}); CHECK(sensorChanged.type == sf::Sensor::Type{});
CHECK(sensorChanged.value == sf::Vector3f()); 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");
}
} }