diff options
author | Loek Le Blansch <loek@pipeframe.xyz> | 2024-11-21 09:30:51 +0100 |
---|---|---|
committer | Loek Le Blansch <loek@pipeframe.xyz> | 2024-11-21 09:30:51 +0100 |
commit | e2f085c444a8b37af65816e10bf366e6860d25c2 (patch) | |
tree | cec8c3e5640f659478838534ad64b86502bc8e29 | |
parent | 9d66b6cfccd15a1600c30af5e744e8b0710eeae6 (diff) | |
parent | 1cc120a0031cfc19c35240da8390d9129b4d75a3 (diff) |
merge `master` into `loek/util`
m--------- | lib/sdl2 | 0 | ||||
m--------- | lib/sdl_image | 0 | ||||
m--------- | lib/sdl_ttf | 0 | ||||
-rw-r--r-- | mwe/events/include/event.h | 2 | ||||
-rw-r--r-- | src/crepe/api/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/crepe/api/Event.h | 112 | ||||
-rw-r--r-- | src/crepe/api/EventHandler.cpp | 5 | ||||
-rw-r--r-- | src/crepe/api/EventHandler.h | 96 | ||||
-rw-r--r-- | src/crepe/api/EventHandler.hpp | 18 | ||||
-rw-r--r-- | src/crepe/api/EventManager.cpp | 46 | ||||
-rw-r--r-- | src/crepe/api/EventManager.h | 161 | ||||
-rw-r--r-- | src/crepe/api/EventManager.hpp | 36 | ||||
-rw-r--r-- | src/crepe/api/IKeyListener.cpp | 19 | ||||
-rw-r--r-- | src/crepe/api/IKeyListener.h | 49 | ||||
-rw-r--r-- | src/crepe/api/IMouseListener.cpp | 29 | ||||
-rw-r--r-- | src/crepe/api/IMouseListener.h | 72 | ||||
-rw-r--r-- | src/crepe/api/KeyCodes.h | 153 | ||||
-rw-r--r-- | src/crepe/facade/SDLContext.cpp | 4 | ||||
-rw-r--r-- | src/test/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/test/DBTest.cpp | 3 | ||||
-rw-r--r-- | src/test/EventTest.cpp | 254 | ||||
-rw-r--r-- | src/test/ValueBrokerTest.cpp | 5 |
22 files changed, 1068 insertions, 8 deletions
diff --git a/lib/sdl2 b/lib/sdl2 -Subproject 9519b9916cd29a14587af0507292f2bd31dd575 +Subproject c98c4fbff6d8f3016a3ce6685bf8f43433c3efc diff --git a/lib/sdl_image b/lib/sdl_image -Subproject abcf63aa71b4e3ac32120fa9870a6500ddcdcc8 +Subproject 56560190a6c5b5740e5afdce747e2a2bfbf16db diff --git a/lib/sdl_ttf b/lib/sdl_ttf -Subproject 4a318f8dfaa1bb6f10e0c5e54052e25d3c7f344 +Subproject a3d0895c1b60c41ff9e85d9203ddd7485c014da diff --git a/mwe/events/include/event.h b/mwe/events/include/event.h index e1b220b..ee1bf52 100644 --- a/mwe/events/include/event.h +++ b/mwe/events/include/event.h @@ -148,7 +148,7 @@ private: }; class ShutDownEvent : public Event { public: - ShutDownEvent() : Event("ShutDownEvent"){}; + ShutDownEvent() : Event("ShutDownEvent") {}; REGISTER_EVENT_TYPE(ShutDownEvent) diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 6557656..d6b6801 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -17,9 +17,13 @@ target_sources(crepe PUBLIC Vector2.cpp Camera.cpp Animator.cpp + EventManager.cpp + IKeyListener.cpp + IMouseListener.cpp LoopManager.cpp LoopTimer.cpp Asset.cpp + EventHandler.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -44,6 +48,13 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES SceneManager.hpp Camera.h Animator.h + EventManager.h + EventManager.hpp + EventHandler.h + EventHandler.hpp + Event.h + IKeyListener.h + IMouseListener.h LoopManager.h LoopTimer.h Asset.h diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h new file mode 100644 index 0000000..06cf7f3 --- /dev/null +++ b/src/crepe/api/Event.h @@ -0,0 +1,112 @@ +// TODO discussing the location of these events +#pragma once + +#include <string> + +#include "KeyCodes.h" + +/** + * \brief Base class for all event types in the system. + */ +class Event {}; + +/** + * \brief Event triggered when a key is pressed. + */ +class KeyPressEvent : public Event { +public: + //! false if first time press, true if key is repeated + bool repeat = false; + + //! The key that was pressed. + Keycode key = Keycode::NONE; +}; + +/** + * \brief Event triggered when a key is released. + */ +class KeyReleaseEvent : public Event { +public: + //! The key that was released. + Keycode key = Keycode::NONE; +}; + +/** + * \brief Event triggered when a mouse button is pressed. + */ +class MousePressEvent : public Event { +public: + //! X-coordinate of the mouse position at the time of the event. + int mouse_x = 0; + + //! Y-coordinate of the mouse position at the time of the event. + int mouse_y = 0; + + //! The mouse button that was pressed. + MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when a mouse button is clicked (press and release). + */ +class MouseClickEvent : public Event { +public: + //! X-coordinate of the mouse position at the time of the event. + int mouse_x = 0; + + //! Y-coordinate of the mouse position at the time of the event. + int mouse_y = 0; + + //! The mouse button that was clicked. + MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when a mouse button is released. + */ +class MouseReleaseEvent : public Event { +public: + //! X-coordinate of the mouse position at the time of the event. + int mouse_x = 0; + + //! Y-coordinate of the mouse position at the time of the event. + int mouse_y = 0; + + //! The mouse button that was released. + MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when the mouse is moved. + */ +class MouseMoveEvent : public Event { +public: + //! X-coordinate of the mouse position at the time of the event. + int mouse_x = 0; + + //! Y-coordinate of the mouse position at the time of the event. + int mouse_y = 0; +}; + +/** + * \brief Event triggered during a collision between objects. + */ +class CollisionEvent : public Event { +public: + //! Data describing the collision (currently not implemented). + // Collision collisionData; +}; + +/** + * \brief Event triggered when text is submitted, e.g., from a text input. + */ +class TextSubmitEvent : public Event { +public: + //! The submitted text. + std::string text = ""; +}; + +/** + * \brief Event triggered to indicate the application is shutting down. + */ +class ShutDownEvent : public Event {}; diff --git a/src/crepe/api/EventHandler.cpp b/src/crepe/api/EventHandler.cpp new file mode 100644 index 0000000..4dc232f --- /dev/null +++ b/src/crepe/api/EventHandler.cpp @@ -0,0 +1,5 @@ +#include "EventHandler.h" + +using namespace crepe; + +bool IEventHandlerWrapper::exec(const Event & e) { return this->call(e); } diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h new file mode 100644 index 0000000..ef659fd --- /dev/null +++ b/src/crepe/api/EventHandler.h @@ -0,0 +1,96 @@ +#pragma once + +#include <functional> +#include <string> + +#include "Event.h" + +namespace crepe { +/** + * \brief A type alias for an event handler function. + * + * The EventHandler is a std::function that takes an EventType reference and returns a boolean value + * indicating whether the event is handled. + * + * \tparam EventType The type of event this handler will handle. + * + * Returning \c false from an event handler results in the event being propogated to other listeners for the same event type, while returning \c true stops propogation altogether. + */ +template <typename EventType> +using EventHandler = std::function<bool(const EventType & e)>; + +/** + * \class IEventHandlerWrapper + * \brief An abstract base class for event handler wrappers. + * + * This class provides the interface for handling events. Derived classes must implement the + * `call()` method to process events + */ +class IEventHandlerWrapper { +public: + /** + * \brief Virtual destructor for IEventHandlerWrapper. + */ + virtual ~IEventHandlerWrapper() = default; + + /** + * \brief Executes the handler with the given event. + * + * This method calls the `call()` method of the derived class, passing the event to the handler. + * + * \param e The event to be processed. + * \return A boolean value indicating whether the event is handled. + */ + bool exec(const Event & e); + +private: + /** + * \brief The method responsible for handling the event. + * + * This method is implemented by derived classes to process the event. + * + * \param e The event to be processed. + * \return A boolean value indicating whether the event is handled. + */ + virtual bool call(const Event & e) = 0; +}; + +/** + * \class EventHandlerWrapper + * \brief A wrapper for event handler functions. + * + * This class wraps an event handler function of a specific event type. It implements the + * `call()` and `get_type()` methods to allow the handler to be executed and its type to be + * queried. + * + * \tparam EventType The type of event this handler will handle. + */ +template <typename EventType> +class EventHandlerWrapper : public IEventHandlerWrapper { +public: + /** + * \brief Constructs an EventHandlerWrapper with a given handler. + * + * The constructor takes an event handler function and stores it in the wrapper. + * + * \param handler The event handler function. + */ + explicit EventHandlerWrapper(const EventHandler<EventType> & handler); + +private: + /** + * \brief Calls the stored event handler with the event. + * + * This method casts the event to the appropriate type and calls the handler. + * + * \param e The event to be handled. + * \return A boolean value indicating whether the event is handled. + */ + bool call(const Event & e) override; + //! The event handler function. + EventHandler<EventType> handler; +}; + +} // namespace crepe + +#include "EventHandler.hpp" diff --git a/src/crepe/api/EventHandler.hpp b/src/crepe/api/EventHandler.hpp new file mode 100644 index 0000000..391dcca --- /dev/null +++ b/src/crepe/api/EventHandler.hpp @@ -0,0 +1,18 @@ +#include <typeindex> + +#include "EventHandler.h" + +namespace crepe { + +// Implementation of EventHandlerWrapper constructor +template <typename EventType> +EventHandlerWrapper<EventType>::EventHandlerWrapper(const EventHandler<EventType> & handler) + : handler(handler) {} + +// Implementation of EventHandlerWrapper::call +template <typename EventType> +bool EventHandlerWrapper<EventType>::call(const Event & e) { + return this->handler(static_cast<const EventType &>(e)); +} + +} //namespace crepe diff --git a/src/crepe/api/EventManager.cpp b/src/crepe/api/EventManager.cpp new file mode 100644 index 0000000..20f0dd3 --- /dev/null +++ b/src/crepe/api/EventManager.cpp @@ -0,0 +1,46 @@ +#include "EventManager.h" + +using namespace crepe; +using namespace std; + +EventManager & EventManager::get_instance() { + static EventManager instance; + return instance; +} + +void EventManager::dispatch_events() { + for (auto & event : this->events_queue) { + this->handle_event(event.type, event.channel, *event.event.get()); + } + this->events_queue.clear(); +} + +void EventManager::handle_event(type_index type, event_channel_t channel, const Event & data) { + auto handlers_it = this->subscribers.find(type); + if (handlers_it == this->subscribers.end()) return; + + vector<CallbackEntry> & handlers = handlers_it->second; + for (auto & handler : handlers) { + bool check_channel = handler.channel != CHANNEL_ALL || channel != CHANNEL_ALL; + if (check_channel && handler.channel != channel) continue; + + bool handled = handler.callback->exec(data); + if (handled) return; + } +} + +void EventManager::clear() { + this->subscribers.clear(); + this->events_queue.clear(); +} + +void EventManager::unsubscribe(subscription_t id) { + for (auto & [event_type, handlers] : this->subscribers) { + for (auto it = handlers.begin(); it != handlers.end(); it++) { + // find listener with subscription id + if ((*it).id != id) continue; + it = handlers.erase(it); + return; + } + } +} diff --git a/src/crepe/api/EventManager.h b/src/crepe/api/EventManager.h new file mode 100644 index 0000000..348a04d --- /dev/null +++ b/src/crepe/api/EventManager.h @@ -0,0 +1,161 @@ +#pragma once + +#include <memory> +#include <typeindex> +#include <unordered_map> +#include <vector> + +#include "Event.h" +#include "EventHandler.h" + +namespace crepe { + +//! Event listener unique ID +typedef size_t subscription_t; + +/** + * \brief Event channel + * + * Events can be sent to a specific channel, which prevents listeners on other channels from + * being called. The default channel is EventManager::CHANNEL_ALL, which calls all listeners. + */ +typedef size_t event_channel_t; + +/** + * \class EventManager + * \brief Manages event subscriptions, triggers, and queues, enabling decoupled event handling. + * + * The `EventManager` acts as a centralized event system. It allows for registering callbacks + * for specific event types, triggering events synchronously, queueing events for later + * processing, and managing subscriptions via unique identifiers. + */ +class EventManager { +public: + static constexpr const event_channel_t CHANNEL_ALL = -1; + + /** + * \brief Get the singleton instance of the EventManager. + * + * This method returns the unique instance of the EventManager, creating it if it + * doesn't already exist. Ensures only one instance is active in the program. + * + * \return Reference to the singleton instance of the EventManager. + */ + static EventManager & get_instance(); + + /** + * \brief Subscribe to a specific event type. + * + * Registers a callback for a given event type and optional channel. Each callback + * is assigned a unique subscription ID that can be used for later unsubscription. + * + * \tparam EventType The type of the event to subscribe to. + * \param callback The callback function to be invoked when the event is triggered. + * \param channel The channel number to subscribe to (default is CHANNEL_ALL, which listens to all channels). + * \return A unique subscription ID associated with the registered callback. + */ + template <typename EventType> + subscription_t subscribe(const EventHandler<EventType> & callback, + event_channel_t channel = CHANNEL_ALL); + + /** + * \brief Unsubscribe a previously registered callback. + * + * Removes a callback from the subscription list based on its unique subscription ID. + * + * \param event_id The unique subscription ID of the callback to remove. + */ + void unsubscribe(subscription_t event_id); + + /** + * \brief Trigger an event immediately. + * + * Synchronously invokes all registered callbacks for the given event type on the specified channel. + * + * \tparam EventType The type of the event to trigger. + * \param event The event instance to pass to the callbacks. + * \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels). + */ + template <typename EventType> + void trigger_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); + + /** + * \brief Queue an event for later processing. + * + * Adds an event to the event queue to be processed during the next call to `dispatch_events`. + * + * \tparam EventType The type of the event to queue. + * \param event The event instance to queue. + * \param channel The channel to associate with the event (default is CHANNEL_ALL). + */ + template <typename EventType> + void queue_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); + + /** + * \brief Process all queued events. + * + * Iterates through the event queue and triggers callbacks for each queued event. + * Events are removed from the queue once processed. + */ + void dispatch_events(); + + /** + * \brief Clear all subscriptions. + * + * Removes all registered event handlers and clears the subscription list. + */ + void clear(); + +private: + /** + * \brief Default constructor for the EventManager. + * + * Constructor is private to enforce the singleton pattern. + */ + EventManager() = default; + + /** + * \struct QueueEntry + * \brief Represents an entry in the event queue. + */ + struct QueueEntry { + std::unique_ptr<Event> event; ///< The event instance. + event_channel_t channel = CHANNEL_ALL; ///< The channel associated with the event. + std::type_index type; ///< The type of the event. + }; + + /** + * \brief Internal event handler + * + * This function processes a single event, and is used to process events both during + * EventManager::dispatch_events and inside EventManager::trigger_event + * + * \param type \c typeid of concrete Event class + * \param channel Event channel + * \param data Event data + */ + void handle_event(std::type_index type, event_channel_t channel, const Event & data); + + /** + * \struct CallbackEntry + * \brief Represents a registered event handler callback. + */ + struct CallbackEntry { + std::unique_ptr<IEventHandlerWrapper> callback; ///< The callback function wrapper. + event_channel_t channel = CHANNEL_ALL; ///< The channel this callback listens to. + subscription_t id = -1; ///< Unique subscription ID. + }; + + //! The queue of events to be processed during dispatch. + std::vector<QueueEntry> events_queue; + + //! A map of event type to registered callbacks. + std::unordered_map<std::type_index, std::vector<CallbackEntry>> subscribers; + + //! Counter to generate unique subscription IDs. + subscription_t subscription_counter = 0; +}; + +} // namespace crepe + +#include "EventManager.hpp" diff --git a/src/crepe/api/EventManager.hpp b/src/crepe/api/EventManager.hpp new file mode 100644 index 0000000..a5f4556 --- /dev/null +++ b/src/crepe/api/EventManager.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "EventManager.h" + +namespace crepe { + +template <typename EventType> +subscription_t EventManager::subscribe(const EventHandler<EventType> & callback, + event_channel_t channel) { + subscription_counter++; + std::type_index event_type = typeid(EventType); + std::unique_ptr<EventHandlerWrapper<EventType>> handler + = std::make_unique<EventHandlerWrapper<EventType>>(callback); + std::vector<CallbackEntry> & handlers = this->subscribers[event_type]; + handlers.emplace_back(CallbackEntry{ + .callback = std::move(handler), .channel = channel, .id = subscription_counter}); + return subscription_counter; +} + +template <typename EventType> +void EventManager::queue_event(const EventType & event, event_channel_t channel) { + static_assert(std::is_base_of<Event, EventType>::value, + "EventType must derive from Event"); + this->events_queue.push_back(QueueEntry{ + .event = std::make_unique<EventType>(event), + .channel = channel, + .type = typeid(EventType), + }); +} + +template <typename EventType> +void EventManager::trigger_event(const EventType & event, event_channel_t channel) { + this->handle_event(typeid(EventType), channel, event); +} + +} // namespace crepe diff --git a/src/crepe/api/IKeyListener.cpp b/src/crepe/api/IKeyListener.cpp new file mode 100644 index 0000000..8642655 --- /dev/null +++ b/src/crepe/api/IKeyListener.cpp @@ -0,0 +1,19 @@ +#include "IKeyListener.h" + +using namespace crepe; + +// Constructor with specified channel +IKeyListener::IKeyListener(event_channel_t channel) + : event_manager(EventManager::get_instance()) { + this->press_id = event_manager.subscribe<KeyPressEvent>( + [this](const KeyPressEvent & event) { return this->on_key_pressed(event); }, channel); + this->release_id = event_manager.subscribe<KeyReleaseEvent>( + [this](const KeyReleaseEvent & event) { return this->on_key_released(event); }, + channel); +} + +// Destructor, unsubscribe events +IKeyListener::~IKeyListener() { + event_manager.unsubscribe(this->press_id); + event_manager.unsubscribe(this->release_id); +} diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h new file mode 100644 index 0000000..328a4c2 --- /dev/null +++ b/src/crepe/api/IKeyListener.h @@ -0,0 +1,49 @@ +#pragma once + +#include "Event.h" +#include "EventHandler.h" +#include "EventManager.h" + +namespace crepe { + +/** + * \class IKeyListener + * \brief Interface for keyboard event handling in the application. + */ +class IKeyListener { +public: + /** + * \brief Constructs an IKeyListener with a specified channel. + * \param channel The channel ID for event handling. + */ + IKeyListener(event_channel_t channel = EventManager::CHANNEL_ALL); + virtual ~IKeyListener(); + IKeyListener(const IKeyListener &) = delete; + IKeyListener & operator=(const IKeyListener &) = delete; + IKeyListener & operator=(IKeyListener &&) = delete; + IKeyListener(IKeyListener &&) = delete; + + /** + * \brief Pure virtual function to handle key press events. + * \param event The key press event to handle. + * \return True if the event was handled, false otherwise. + */ + virtual bool on_key_pressed(const KeyPressEvent & event) = 0; + + /** + * \brief Pure virtual function to handle key release events. + * \param event The key release event to handle. + * \return True if the event was handled, false otherwise. + */ + virtual bool on_key_released(const KeyReleaseEvent & event) = 0; + +private: + //! Key press event id + subscription_t press_id = -1; + //! Key release event id + subscription_t release_id = -1; + //! EventManager reference + EventManager & event_manager; +}; + +} // namespace crepe diff --git a/src/crepe/api/IMouseListener.cpp b/src/crepe/api/IMouseListener.cpp new file mode 100644 index 0000000..989aeb3 --- /dev/null +++ b/src/crepe/api/IMouseListener.cpp @@ -0,0 +1,29 @@ +#include "IMouseListener.h" + +using namespace crepe; + +IMouseListener::IMouseListener(event_channel_t channel) + : event_manager(EventManager::get_instance()) { + this->click_id = event_manager.subscribe<MouseClickEvent>( + [this](const MouseClickEvent & event) { return this->on_mouse_clicked(event); }, + channel); + + this->press_id = event_manager.subscribe<MousePressEvent>( + [this](const MousePressEvent & event) { return this->on_mouse_pressed(event); }, + channel); + + this->release_id = event_manager.subscribe<MouseReleaseEvent>( + [this](const MouseReleaseEvent & event) { return this->on_mouse_released(event); }, + channel); + + this->move_id = event_manager.subscribe<MouseMoveEvent>( + [this](const MouseMoveEvent & event) { return this->on_mouse_moved(event); }, channel); +} + +IMouseListener::~IMouseListener() { + // Unsubscribe event handlers + event_manager.unsubscribe(this->click_id); + event_manager.unsubscribe(this->press_id); + event_manager.unsubscribe(this->release_id); + event_manager.unsubscribe(this->move_id); +} diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h new file mode 100644 index 0000000..15e1619 --- /dev/null +++ b/src/crepe/api/IMouseListener.h @@ -0,0 +1,72 @@ +#pragma once + +#include "Event.h" +#include "EventHandler.h" +#include "EventManager.h" + +namespace crepe { + +/** + * \class IMouseListener + * \brief Interface for mouse event handling in the application. + */ +class IMouseListener { +public: + /** + * \brief Constructs an IMouseListener with a specified channel. + * \param channel The channel ID for event handling. + */ + IMouseListener(event_channel_t channel = EventManager::CHANNEL_ALL); + virtual ~IMouseListener(); + IMouseListener & operator=(const IMouseListener &) = delete; + IMouseListener(const IMouseListener &) = delete; + IMouseListener & operator=(const IMouseListener &&) = delete; + IMouseListener(IMouseListener &&) = delete; + + /** + * \brief Move assignment operator (deleted). + */ + IMouseListener & operator=(IMouseListener &&) = delete; + + /** + * \brief Handles a mouse click event. + * \param event The mouse click event to handle. + * \return True if the event was handled, false otherwise. + */ + virtual bool on_mouse_clicked(const MouseClickEvent & event) = 0; + + /** + * \brief Handles a mouse press event. + * \param event The mouse press event to handle. + * \return True if the event was handled, false otherwise. + */ + virtual bool on_mouse_pressed(const MousePressEvent & event) = 0; + + /** + * \brief Handles a mouse release event. + * \param event The mouse release event to handle. + * \return True if the event was handled, false otherwise. + */ + virtual bool on_mouse_released(const MouseReleaseEvent & event) = 0; + + /** + * \brief Handles a mouse move event. + * \param event The mouse move event to handle. + * \return True if the event was handled, false otherwise. + */ + virtual bool on_mouse_moved(const MouseMoveEvent & event) = 0; + +private: + //! Mouse click event id + subscription_t click_id = -1; + //! Mouse press event id + subscription_t press_id = -1; + //! Mouse release event id + subscription_t release_id = -1; + //! Mouse move event id + subscription_t move_id = -1; + //! EventManager reference + EventManager & event_manager; +}; + +} //namespace crepe diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h new file mode 100644 index 0000000..9e173e0 --- /dev/null +++ b/src/crepe/api/KeyCodes.h @@ -0,0 +1,153 @@ +#pragma once + +//! Enumeration for mouse button inputs, including standard and extended buttons. +enum class MouseButton { + NONE = 0, //!< No mouse button input. + LEFT_MOUSE = 1, //!< Left mouse button. + RIGHT_MOUSE = 2, //!< Right mouse button. + MIDDLE_MOUSE = 3, //!< Middle mouse button (scroll wheel press). + X1_MOUSE = 4, //!< First extended mouse button. + X2_MOUSE = 5, //!< Second extended mouse button. + SCROLL_UP = 6, //!< Scroll wheel upward movement. + SCROLL_DOWN = 7, //!< Scroll wheel downward movement. +}; + +//! Enumeration for keyboard key inputs, including printable characters, function keys, and keypad keys. +enum class Keycode { + NONE = 0, //!< No key input. + SPACE = 32, //!< Spacebar. + APOSTROPHE = 39, //!< Apostrophe ('). + COMMA = 44, //!< Comma (,). + MINUS = 45, //!< Minus (-). + PERIOD = 46, //!< Period (.). + SLASH = 47, //!< Slash (/). + D0 = 48, //!< Digit 0. + D1 = 49, //!< Digit 1. + D2 = 50, //!< Digit 2. + D3 = 51, //!< Digit 3. + D4 = 52, //!< Digit 4. + D5 = 53, //!< Digit 5. + D6 = 54, //!< Digit 6. + D7 = 55, //!< Digit 7. + D8 = 56, //!< Digit 8. + D9 = 57, //!< Digit 9. + SEMICOLON = 59, //!< Semicolon (;). + EQUAL = 61, //!< Equal sign (=). + A = 65, //!< Key 'A'. + B = 66, //!< Key 'B'. + C = 67, //!< Key 'C'. + D = 68, //!< Key 'D'. + E = 69, //!< Key 'E'. + F = 70, //!< Key 'F'. + G = 71, //!< Key 'G'. + H = 72, //!< Key 'H'. + I = 73, //!< Key 'I'. + J = 74, //!< Key 'J'. + K = 75, //!< Key 'K'. + L = 76, //!< Key 'L'. + M = 77, //!< Key 'M'. + N = 78, //!< Key 'N'. + O = 79, //!< Key 'O'. + P = 80, //!< Key 'P'. + Q = 81, //!< Key 'Q'. + R = 82, //!< Key 'R'. + S = 83, //!< Key 'S'. + T = 84, //!< Key 'T'. + U = 85, //!< Key 'U'. + V = 86, //!< Key 'V'. + W = 87, //!< Key 'W'. + X = 88, //!< Key 'X'. + Y = 89, //!< Key 'Y'. + Z = 90, //!< Key 'Z'. + LEFT_BRACKET = 91, //!< Left bracket ([). + BACKSLASH = 92, //!< Backslash (\). + RIGHT_BRACKET = 93, //!< Right bracket (]). + GRAVE_ACCENT = 96, //!< Grave accent (`). + WORLD1 = 161, //!< Non-US key #1. + WORLD2 = 162, //!< Non-US key #2. + ESCAPE = 256, //!< Escape key. + ENTER = 257, //!< Enter key. + TAB = 258, //!< Tab key. + BACKSPACE = 259, //!< Backspace key. + INSERT = 260, //!< Insert key. + DELETE = 261, //!< Delete key. + RIGHT = 262, //!< Right arrow key. + LEFT = 263, //!< Left arrow key. + DOWN = 264, //!< Down arrow key. + UP = 265, //!< Up arrow key. + PAGE_UP = 266, //!< Page Up key. + PAGE_DOWN = 267, //!< Page Down key. + HOME = 268, //!< Home key. + END = 269, //!< End key. + CAPS_LOCK = 280, //!< Caps Lock key. + SCROLL_LOCK = 281, //!< Scroll Lock key. + NUM_LOCK = 282, //!< Num Lock key. + PRINT_SCREEN = 283, //!< Print Screen key. + PAUSE = 284, //!< Pause key. + /** + * \name Function keys (F1-F25). + * \{ + */ + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + /// \} + /** + * \name Keypad digits and operators. + * \{ + */ + KP0 = 320, + KP1 = 321, + KP2 = 322, + KP3 = 323, + KP4 = 324, + KP5 = 325, + KP6 = 326, + KP7 = 327, + KP8 = 328, + KP9 = 329, + KP_DECIMAL = 330, + KP_DIVIDE = 331, + KP_MULTIPLY = 332, + KP_SUBTRACT = 333, + KP_ADD = 334, + KP_ENTER = 335, + KP_EQUAL = 336, + /// \} + /** + * \name Modifier keys. + * \{ + */ + LEFT_SHIFT = 340, + LEFT_CONTROL = 341, + LEFT_ALT = 342, + LEFT_SUPER = 343, + RIGHT_SHIFT = 344, + RIGHT_CONTROL = 345, + RIGHT_ALT = 346, + RIGHT_SUPER = 347, + /// \} + MENU = 348, //!< Menu key. +}; diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index d352ea6..00523a6 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -47,7 +47,8 @@ SDLContext::SDLContext() { SDL_Renderer * tmp_renderer = SDL_CreateRenderer(this->game_window.get(), -1, SDL_RENDERER_ACCELERATED); if (!tmp_renderer) { - throw runtime_error(format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError())); + throw runtime_error( + format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError())); } this->game_renderer @@ -71,7 +72,6 @@ SDLContext::~SDLContext() { IMG_Quit(); SDL_Quit(); } - void SDLContext::handle_events(bool & running) { //TODO: wouter i need events /* diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 7310bb8..8cb4232 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(test_main PUBLIC AssetTest.cpp OptionalRefTest.cpp RenderSystemTest.cpp + EventTest.cpp ECSTest.cpp SceneManagerTest.cpp ValueBrokerTest.cpp diff --git a/src/test/DBTest.cpp b/src/test/DBTest.cpp index b57eba9..e80814c 100644 --- a/src/test/DBTest.cpp +++ b/src/test/DBTest.cpp @@ -1,5 +1,5 @@ -#include <gtest/gtest.h> #include <crepe/facade/DB.h> +#include <gtest/gtest.h> using namespace std; using namespace crepe; @@ -26,4 +26,3 @@ TEST_F(DBTest, Has) { db.set("foo", "bar"); EXPECT_EQ(db.has("foo"), true); } - diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp new file mode 100644 index 0000000..b0e6c9c --- /dev/null +++ b/src/test/EventTest.cpp @@ -0,0 +1,254 @@ + +#include "api/Event.h" +#include "api/EventManager.h" +#include "api/IKeyListener.h" +#include "api/IMouseListener.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class EventManagerTest : public ::testing::Test { +protected: + void SetUp() override { + // Clear any existing subscriptions or events before each test + EventManager::get_instance().clear(); + } + + void TearDown() override { + // Ensure cleanup after each test + EventManager::get_instance().clear(); + } +}; +class MockKeyListener : public IKeyListener { +public: + MOCK_METHOD(bool, on_key_pressed, (const KeyPressEvent & event), (override)); + MOCK_METHOD(bool, on_key_released, (const KeyReleaseEvent & event), (override)); +}; + +class MockMouseListener : public IMouseListener { +public: + MOCK_METHOD(bool, on_mouse_clicked, (const MouseClickEvent & event), (override)); + MOCK_METHOD(bool, on_mouse_pressed, (const MousePressEvent & event), (override)); + MOCK_METHOD(bool, on_mouse_released, (const MouseReleaseEvent & event), (override)); + MOCK_METHOD(bool, on_mouse_moved, (const MouseMoveEvent & event), (override)); +}; + +TEST_F(EventManagerTest, EventSubscription) { + EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { + std::cout << "Key Event Triggered" << std::endl; + return true; + }; + + // Subscribe to KeyPressEvent + EventManager::get_instance().subscribe<KeyPressEvent>(key_handler, 1); + + // Verify subscription (not directly verifiable; test by triggering event) + + EventManager::get_instance().trigger_event<KeyPressEvent>( + KeyPressEvent{ + .repeat = true, + .key = Keycode::A, + }, + 1); + EventManager::get_instance().trigger_event<KeyPressEvent>( + KeyPressEvent{ + .repeat = true, + .key = Keycode::A, + + }, + EventManager::CHANNEL_ALL); +} +TEST_F(EventManagerTest, EventManagerTest_trigger_all_channels) { + bool triggered = false; + + EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { + triggered = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; + }; + EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, + EventManager::CHANNEL_ALL); + + MouseClickEvent click_event{ + .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; + EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, + EventManager::CHANNEL_ALL); + + EXPECT_TRUE(triggered); +} +TEST_F(EventManagerTest, EventManagerTest_trigger_one_channel) { + bool triggered = false; + int test_channel = 1; + EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { + triggered = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; + }; + EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, test_channel); + + MouseClickEvent click_event{ + .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; + EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, + EventManager::CHANNEL_ALL); + + EXPECT_FALSE(triggered); + EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, test_channel); +} + +TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { + EventManager & event_manager = EventManager::get_instance(); + + // Flags to track handler calls + bool triggered_true = false; + bool triggered_false = false; + + // Handlers + EventHandler<MouseClickEvent> mouse_handler_true = [&](const MouseClickEvent & e) { + triggered_true = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return true; // Stops propagation + }; + + EventHandler<MouseClickEvent> mouse_handler_false = [&](const MouseClickEvent & e) { + triggered_false = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + + // Test event + MouseClickEvent click_event{ + .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; + event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + + // Trigger event + event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + + // Check that only the true handler was triggered + EXPECT_TRUE(triggered_true); + EXPECT_FALSE(triggered_false); + + // Reset and clear + triggered_true = false; + triggered_false = false; + event_manager.clear(); + event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + + // Trigger event again + event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + + // Check that both handlers were triggered + EXPECT_TRUE(triggered_true); + EXPECT_TRUE(triggered_false); +} + +TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) { + EventManager & event_manager = EventManager::get_instance(); + bool triggered1 = false; + bool triggered2 = false; + int test_channel = 1; + EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { + triggered1 = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { + triggered2 = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + event_manager.subscribe<MouseClickEvent>(mouse_handler1); + event_manager.subscribe<MouseClickEvent>(mouse_handler2, test_channel); + + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}, + test_channel); + event_manager.dispatch_events(); + EXPECT_TRUE(triggered1); + EXPECT_TRUE(triggered2); +} + +TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { + EventManager & event_manager = EventManager::get_instance(); + + // Flags to track if handlers are triggered + bool triggered1 = false; + bool triggered2 = false; + + // Define EventHandlers + EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { + triggered1 = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + + EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { + triggered2 = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + // Subscribe handlers + subscription_t handler1_id = event_manager.subscribe<MouseClickEvent>(mouse_handler1); + subscription_t handler2_id = event_manager.subscribe<MouseClickEvent>(mouse_handler2); + + // Queue events + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - both handlers should be triggered + event_manager.dispatch_events(); + EXPECT_TRUE(triggered1); // Handler 1 should be triggered + EXPECT_TRUE(triggered2); // Handler 2 should be triggered + + // Reset flags + triggered1 = false; + triggered2 = false; + + // Unsubscribe handler1 + event_manager.unsubscribe(handler1_id); + + // Queue the same event again + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - only handler 2 should be triggered, handler 1 should NOT + event_manager.dispatch_events(); + EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered + EXPECT_TRUE(triggered2); // Handler 2 should be triggered + + // Reset flags + triggered2 = false; + + // Unsubscribe handler2 + event_manager.unsubscribe(handler2_id); + + // Queue the event again + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - no handler should be triggered + event_manager.dispatch_events(); + EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered + EXPECT_FALSE(triggered2); // Handler 2 should NOT be triggered +} diff --git a/src/test/ValueBrokerTest.cpp b/src/test/ValueBrokerTest.cpp index 10a4654..e6bb058 100644 --- a/src/test/ValueBrokerTest.cpp +++ b/src/test/ValueBrokerTest.cpp @@ -13,7 +13,7 @@ public: int write_count = 0; int value = 0; - ValueBroker<int> broker { + ValueBroker<int> broker{ [this](const int & target) -> void { this->write_count++; this->value = target; @@ -49,7 +49,7 @@ TEST_F(ValueBrokerTest, ProxyWrite) { EXPECT_EQ(write_count, 1); } -void dummy(int) { } +void dummy(int) {} TEST_F(ValueBrokerTest, ProxyRead) { dummy(proxy); EXPECT_EQ(read_count, 1); @@ -61,4 +61,3 @@ TEST_F(ValueBrokerTest, ProxyReadWrite) { ASSERT_EQ(read_count, 1); ASSERT_EQ(write_count, 1); } - |