From 85e5ce5d65b0d6b8e40118844cef95330e9f67d4 Mon Sep 17 00:00:00 2001 From: binary1248 Date: Sun, 30 Jun 2024 16:13:51 +0200 Subject: [PATCH] Added event handling example. --- examples/CMakeLists.txt | 1 + examples/event_handling/CMakeLists.txt | 7 + examples/event_handling/EventHandling.cpp | 338 ++++++++++++++++++++ examples/event_handling/resources/tuffy.ttf | Bin 0 -> 18444 bytes examples/raw_input/RawInput.cpp | 2 +- 5 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 examples/event_handling/CMakeLists.txt create mode 100644 examples/event_handling/EventHandling.cpp create mode 100644 examples/event_handling/resources/tuffy.ttf diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fcff403c6..2ee9b16b1 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,6 +19,7 @@ if(SFML_BUILD_WINDOW) endif() if(SFML_BUILD_GRAPHICS) + add_subdirectory(event_handling) add_subdirectory(opengl) add_subdirectory(stencil) diff --git a/examples/event_handling/CMakeLists.txt b/examples/event_handling/CMakeLists.txt new file mode 100644 index 000000000..d8ccb467d --- /dev/null +++ b/examples/event_handling/CMakeLists.txt @@ -0,0 +1,7 @@ +# all source files +set(SRC EventHandling.cpp) + +# define the event_handling target +sfml_add_example(event_handling GUI_APP + SOURCES ${SRC} + DEPENDS SFML::Graphics) diff --git a/examples/event_handling/EventHandling.cpp b/examples/event_handling/EventHandling.cpp new file mode 100644 index 000000000..6211611bb --- /dev/null +++ b/examples/event_handling/EventHandling.cpp @@ -0,0 +1,338 @@ +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + + +namespace +{ +std::string vec2ToString(const sf::Vector2i vec2) +{ + return '(' + std::to_string(vec2.x) + ", " + std::to_string(vec2.y) + ')'; +}; +} // namespace + + +//////////////////////////////////////////////////////////// +/// \brief Application class +/// +//////////////////////////////////////////////////////////// +class Application +{ +public: + //////////////////////////////////////////////////////////// + Application() + { + m_window.setVerticalSyncEnabled(true); + m_logText.setFillColor(sf::Color::White); + m_handlerText.setFillColor(sf::Color::White); + m_handlerText.setStyle(sf::Text::Bold); + m_handlerText.setPosition({380.f, 260.f}); + m_instructions.setFillColor(sf::Color::White); + m_instructions.setStyle(sf::Text::Bold); + m_instructions.setPosition({380.f, 310.f}); + } + + // The visitor we pass to event->visit in the "Visitor" handler + // Make sure all defined operator()s return the same type. + // The operator()s can also have void return type if there is nothing to return. + struct Visitor + { + Visitor(Application& app) : application(app) + { + } + + std::optional operator()(const sf::Event::Closed&) + { + application.m_window.close(); + return std::nullopt; + } + + std::optional operator()(const sf::Event::KeyPressed& keyPress) + { + // When the enter key is pressed, switch to the next handler type + if (keyPress.code == sf::Keyboard::Key::Enter) + { + application.m_handlerType = HandlerType::Overload; + application.m_handlerText.setString("Current Handler: Overload"); + } + + return "Key Pressed: " + sf::Keyboard::getDescription(keyPress.scancode); + } + + std::optional operator()(const sf::Event::MouseMoved& mouseMoved) + { + return "Mouse Moved: " + vec2ToString(mouseMoved.position); + } + + std::optional operator()(const sf::Event::MouseButtonPressed&) + { + return "Mouse Pressed"; + } + + std::optional operator()(const sf::Event::TouchBegan& touchBegan) + { + return "Touch Began: " + vec2ToString(touchBegan.position); + } + + // When defining a visitor, make sure all event types can be handled by it. + // If you don't intend on exhaustively specifying an operator() for each + // event type, you can provide a templated operator() that will be selected + // by overload resolution when no other event type matches. + template + std::optional operator()(const T&) + { + // All unhandled events will end up here + // application.m_log.emplace_back("Other Event"); + return std::nullopt; + } + + Application& application; + }; + + //////////////////////////////////////////////////////////// + void run() + { + // This example is for demonstration purposes only + // All the following forms of event handling have equivalent behavior + // In your own code you should decide which form of event handling + // suits your needs best and use a single form of event handling + while (m_window.isOpen()) + { + if (m_handlerType == HandlerType::Classic) + { + // The "classical form" of event handling + // Poll/Wait for events in a loop and handle them + // individually based on their concrete type + while (const std::optional event = m_window.pollEvent()) + { + if (event->is()) + { + m_window.close(); + break; + } + + if (const auto* keyPress = event->getIf()) + { + m_log.emplace_back("Key Pressed: " + sf::Keyboard::getDescription(keyPress->scancode)); + + // When the enter key is pressed, switch to the next handler type + if (keyPress->code == sf::Keyboard::Key::Enter) + { + m_handlerType = HandlerType::Visitor; + m_handlerText.setString("Current Handler: Visitor"); + } + } + else if (const auto* mouseMoved = event->getIf()) + { + m_log.emplace_back("Mouse Moved: " + vec2ToString(mouseMoved->position)); + } + else if (event->is()) + { + m_log.emplace_back("Mouse Pressed"); + } + else if (const auto* touchBegan = event->getIf()) + { + m_log.emplace_back("Touch Began: " + vec2ToString(touchBegan->position)); + } + else + { + // All unhandled events will end up here + // m_log.emplace_back("Other Event"); + } + } + } + else if (m_handlerType == HandlerType::Visitor) + { + // Event Visitor + // A visitor able to visit all event types is passed to the event + // The visitor's defined operator()s can also return values + while (const std::optional event = m_window.pollEvent()) + { + if (std::optional logMessage = event->visit(Visitor(*this))) + m_log.emplace_back(std::move(*logMessage)); + } + } + else if (m_handlerType == HandlerType::Overload) + { + // Overloaded visitation + // A callable taking a concrete event type is provided per event type you want to handle + m_window.handleEvents([&](const sf::Event::Closed&) { m_window.close(); }, + [&](const sf::Event::KeyPressed& keyPress) + { + m_log.emplace_back( + "Key Pressed: " + sf::Keyboard::getDescription(keyPress.scancode)); + + // When the enter key is pressed, switch to the next handler type + if (keyPress.code == sf::Keyboard::Key::Enter) + { + m_handlerType = HandlerType::Generic; + m_handlerText.setString("Current Handler: Generic"); + } + }, + [&](const sf::Event::MouseMoved& mouseMoved) + { m_log.emplace_back("Mouse Moved: " + vec2ToString(mouseMoved.position)); }, + [&](const sf::Event::MouseButtonPressed&) { m_log.emplace_back("Mouse Pressed"); }, + [&](const sf::Event::TouchBegan& touchBegan) + { m_log.emplace_back("Touch Began: " + vec2ToString(touchBegan.position)); }); + + // To handle unhandled events, just add the following lambda to the set of handlers + // [&](const auto&) { m_log.emplace_back("Other Event"); } + } + else if (m_handlerType == HandlerType::Generic) + { + // Generic visitation + // A generic callable is provided that can differentiate by deduced event type + m_window.handleEvents( + [&](auto&& event) + { + // Remove reference and cv-qualifiers + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + m_window.close(); + } + else if constexpr (std::is_same_v) + { + m_log.emplace_back("Key Pressed: " + sf::Keyboard::getDescription(event.scancode)); + + // When the enter key is pressed, switch to the next handler type + if (event.code == sf::Keyboard::Key::Enter) + { + m_handlerType = HandlerType::Forward; + m_handlerText.setString("Current Handler: Forward"); + } + } + else if constexpr (std::is_same_v) + { + m_log.emplace_back("Mouse Moved: " + vec2ToString(event.position)); + } + else if constexpr (std::is_same_v) + { + m_log.emplace_back("Mouse Pressed"); + } + else if constexpr (std::is_same_v) + { + m_log.emplace_back("Touch Began: " + vec2ToString(event.position)); + } + else + { + // All unhandled events will end up here + // m_log.emplace_back("Other Event"); + } + }); + } + else if (m_handlerType == HandlerType::Forward) + { + // Forward to other callable + // In this case we forward it to our handle member functions + // we defined for the concrete event types we want to handle + // When choosing this method a default "catch-all" handler must + // be available for unhandled events to be forwarded to + // If you don't want to provide an empty "catch-all" handler + // you will have to make sure (e.g. via if constexpr) that this + // lambda doesn't attempt to call a member function that doesn't exist + m_window.handleEvents([this](const auto& event) { handle(event); }); + } + + // Limit the log to 24 entries + if (m_log.size() > 24u) + m_log.erase(m_log.begin(), m_log.begin() + static_cast(m_log.size() - 24u)); + + // Draw the contents of the log to the window + m_window.clear(); + + for (std::size_t i = 0; i < m_log.size(); ++i) + { + m_logText.setPosition({50.f, static_cast(i * 20) + 50.f}); + m_logText.setString(m_log[i]); + m_window.draw(m_logText); + } + + m_window.draw(m_handlerText); + m_window.draw(m_instructions); + m_window.display(); + } + } + + // The following handle methods are called by the forwarding event handler implementation + // A handle method is defined per event type you want to handle + // To handle any other events that are left, the templated handle method will be called + // Overload resolution will prefer the handle methods that fit the event type better + // before falling back to the templated method + void handle(const sf::Event::Closed&) + { + m_window.close(); + } + + void handle(const sf::Event::KeyPressed& keyPress) + { + m_log.emplace_back("Key Pressed: " + sf::Keyboard::getDescription(keyPress.scancode)); + + // When the enter key is pressed, switch to the next handler type + if (keyPress.code == sf::Keyboard::Key::Enter) + { + m_handlerType = HandlerType::Classic; + m_handlerText.setString("Current Handler: Classic"); + } + } + + void handle(const sf::Event::MouseMoved& mouseMoved) + { + m_log.emplace_back("Mouse Moved: " + vec2ToString(mouseMoved.position)); + } + + void handle(const sf::Event::MouseButtonPressed&) + { + m_log.emplace_back("Mouse Pressed"); + } + + void handle(const sf::Event::TouchBegan& touchBegan) + { + m_log.emplace_back("Touch Began: " + vec2ToString(touchBegan.position)); + } + + template + void handle(const T&) + { + // All unhandled events will end up here + // m_log.emplace_back("Other Event"); + } + +private: + enum class HandlerType + { + Classic, + Visitor, + Overload, + Generic, + Forward + }; + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + sf::RenderWindow m_window{sf::VideoMode({800u, 600u}), "SFML Event Handling", sf::Style::Titlebar | sf::Style::Close}; + const sf::Font m_font{sf::Font::openFromFile("resources/tuffy.ttf").value()}; + sf::Text m_logText{m_font, "", 20}; + sf::Text m_handlerText{m_font, "Current Handler: Classic", 24}; + sf::Text m_instructions{m_font, "Press Enter to change handler type", 24}; + std::vector m_log; + HandlerType m_handlerType{HandlerType::Classic}; +}; + + +//////////////////////////////////////////////////////////// +/// Entry point of application +/// +/// \return Application exit code +/// +//////////////////////////////////////////////////////////// +int main() +{ + Application application; + application.run(); +} diff --git a/examples/event_handling/resources/tuffy.ttf b/examples/event_handling/resources/tuffy.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8ea647090f75e7d34bee4c511921299d1f3c1055 GIT binary patch literal 18444 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