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 000000000..8ea647090 Binary files /dev/null and b/examples/event_handling/resources/tuffy.ttf differ diff --git a/examples/raw_input/RawInput.cpp b/examples/raw_input/RawInput.cpp index 114a84965..6168109af 100644 --- a/examples/raw_input/RawInput.cpp +++ b/examples/raw_input/RawInput.cpp @@ -18,7 +18,7 @@ int main() sf::RenderWindow window(sf::VideoMode({800u, 600u}), "SFML Raw Mouse Input", sf::Style::Titlebar | sf::Style::Close); window.setVerticalSyncEnabled(true); - // Open the application font and pass it to the Effect class + // Open the application font const auto font = sf::Font::openFromFile("resources/tuffy.ttf").value(); // Create the mouse position text