diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/crepe/api/Button.cpp | 11 | ||||
| -rw-r--r-- | src/crepe/api/Button.h | 67 | ||||
| -rw-r--r-- | src/crepe/api/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | src/crepe/api/Event.h | 22 | ||||
| -rw-r--r-- | src/crepe/api/EventHandler.h | 58 | ||||
| -rw-r--r-- | src/crepe/api/IKeyListener.h | 22 | ||||
| -rw-r--r-- | src/crepe/api/IMouseListener.h | 42 | ||||
| -rw-r--r-- | src/crepe/api/KeyCodes.h | 21 | ||||
| -rw-r--r-- | src/crepe/api/LoopManager.cpp | 4 | ||||
| -rw-r--r-- | src/crepe/api/UIObject.cpp | 8 | ||||
| -rw-r--r-- | src/crepe/api/UIObject.h | 25 | ||||
| -rw-r--r-- | src/crepe/facade/SDLContext.cpp | 214 | ||||
| -rw-r--r-- | src/crepe/facade/SDLContext.h | 76 | ||||
| -rw-r--r-- | src/crepe/system/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/crepe/system/InputSystem.cpp | 187 | ||||
| -rw-r--r-- | src/crepe/system/InputSystem.h | 85 | ||||
| -rw-r--r-- | src/example/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/example/button.cpp | 54 | ||||
| -rw-r--r-- | src/example/gameloop.cpp | 7 | ||||
| -rw-r--r-- | src/test/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/test/EventTest.cpp | 36 | ||||
| -rw-r--r-- | src/test/InputTest.cpp | 293 | 
22 files changed, 1117 insertions, 125 deletions
| diff --git a/src/crepe/api/Button.cpp b/src/crepe/api/Button.cpp new file mode 100644 index 0000000..76f74f0 --- /dev/null +++ b/src/crepe/api/Button.cpp @@ -0,0 +1,11 @@ +#include "Button.h" + +namespace crepe { + +Button::Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset, +			   const std::function<void()> & on_click, bool is_toggle) +	: UIObject(id, dimensions, offset), +	  is_toggle(is_toggle), +	  on_click(on_click) {} + +} // namespace crepe diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h new file mode 100644 index 0000000..26e7526 --- /dev/null +++ b/src/crepe/api/Button.h @@ -0,0 +1,67 @@ +#pragma once + +#include <functional> + +#include "UIObject.h" + +namespace crepe { + +//! Represents a clickable UI button, derived from the UiObject class. +class Button : public UIObject { +public: +	/** +	 * \brief Constructs a Button with the specified game object ID and dimensions. +	 *  +	 * \param id The unique ID of the game object associated with this button. +	 * \param dimensions The width and height of the UIObject +	 * \param offset The offset relative this GameObjects Transform +	 * \param is_toggle Optional flag to indicate if the button is a toggle button. Defaults to false. +	 * \param on_click callback function that will be invoked when the button is clicked. +	 */ +	Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset, +		   const std::function<void()> & on_click, bool is_toggle = false); + +	/** +	 * \brief Indicates if the button is a toggle button (can be pressed and released). +	 *  +	 * A toggle button allows for a pressed/released state, whereas a regular button +	 * typically only has an on-click state. +	 */ +	bool is_toggle = false; +	// TODO: create separate toggle button class +	/** +	 * \brief The callback function to be executed when the button is clicked. +	 *  +	 * This function is invoked whenever the button is clicked. It can be set to any +	 * function that matches the signature `void()`. +	 */ +	std::function<void()> on_click = nullptr; + +	/** +	 * \brief Callback function to be executed when the mouse enters the button's boundaries. +	 * +	 * This function is triggered when the mouse cursor moves over the button, allowing +	 * custom actions like visual effects, highlighting, or sound effects. +	 */ +	std::function<void()> on_mouse_enter = nullptr; + +	/** +	 * \brief Callback function to be executed when the mouse exits the button's boundaries. +	 * +	 * This function is triggered when the mouse cursor moves out of the button's area, +	 * allowing custom actions like resetting visual effects or playing exit-related effects. +	 */ +	std::function<void()> on_mouse_exit = nullptr; + +private: +	//! friend relation for is_pressed and hover variables +	friend class InputSystem; +	//! Indicates whether the toggle button is pressed +	bool is_pressed = false; +	//! Indicates whether the mouse is currently hovering over the button +	bool hover = false; + +public: +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 0808612..b186e0c 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -19,6 +19,8 @@ target_sources(crepe PUBLIC  	Asset.cpp  	EventHandler.cpp  	Script.cpp +	Button.cpp +	UIObject.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -47,4 +49,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	LoopManager.h  	LoopTimer.h  	Asset.h +	Button.h +	UIObject.h  ) diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index b267e3e..6298118 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -88,9 +88,31 @@ public:  	//! Y-coordinate of the mouse position at the time of the event.  	int mouse_y = 0; + +	// Movement since last event in x +	int delta_x = 0; + +	// Movement since last event in y +	int delta_y = 0;  };  /** + * \brief Event triggered when the mouse is moved. + */ +class MouseScrollEvent : 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; + +	//! scroll direction (-1 = down, 1 = up) +	int scroll_direction = 0; +	//! scroll amount in y axis (from and away from the person). +	float scroll_delta = 0; +}; +/**   * \brief Event triggered during a collision between objects.   */  class CollisionEvent : public Event {}; diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h index ef659fd..7bdd9a3 100644 --- a/src/crepe/api/EventHandler.h +++ b/src/crepe/api/EventHandler.h @@ -29,29 +29,29 @@ using EventHandler = std::function<bool(const EventType & e)>;  class IEventHandlerWrapper {  public:  	/** -     * \brief Virtual destructor for IEventHandlerWrapper. -     */ +	 * \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. -     */ +	 * \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. -     */ +	 * \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;  }; @@ -69,23 +69,23 @@ 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. -     */ +	 * \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. -     */ +	 * \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; diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h index 6ded107..180a0a6 100644 --- a/src/crepe/api/IKeyListener.h +++ b/src/crepe/api/IKeyListener.h @@ -14,9 +14,9 @@ namespace crepe {  class IKeyListener {  public:  	/** -     * \brief Constructs an IKeyListener with a specified channel. -     * \param channel The channel ID for event handling. -     */ +	 * \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; @@ -25,17 +25,17 @@ public:  	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. -     */ +	 * \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. -     */ +	 * \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: diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h index 9e4fdf7..e19897d 100644 --- a/src/crepe/api/IMouseListener.h +++ b/src/crepe/api/IMouseListener.h @@ -14,9 +14,9 @@ namespace crepe {  class IMouseListener {  public:  	/** -     * \brief Constructs an IMouseListener with a specified channel. -     * \param channel The channel ID for event handling. -     */ +	 * \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; @@ -25,36 +25,36 @@ public:  	IMouseListener(IMouseListener &&) = delete;  	/** -     * \brief Move assignment operator (deleted). -     */ +	 * \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. -     */ +	 * \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. -     */ +	 * \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. -     */ +	 * \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. -     */ +	 * \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: diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h index 9e173e0..fcfc080 100644 --- a/src/crepe/api/KeyCodes.h +++ b/src/crepe/api/KeyCodes.h @@ -1,5 +1,5 @@  #pragma once - +namespace crepe {  //! Enumeration for mouse button inputs, including standard and extended buttons.  enum class MouseButton {  	NONE = 0, //!< No mouse button input. @@ -85,9 +85,9 @@ enum class Keycode {  	PRINT_SCREEN = 283, //!< Print Screen key.  	PAUSE = 284, //!< Pause key.  	/** -	 * \name Function keys (F1-F25). -	 * \{ -	 */ +		 * \name Function keys (F1-F25). +		 * \{ +		 */  	F1 = 290,  	F2 = 291,  	F3 = 292, @@ -115,9 +115,9 @@ enum class Keycode {  	F25 = 314,  	/// \}  	/** -	 * \name Keypad digits and operators. -	 * \{ -	 */ +		 * \name Keypad digits and operators. +		 * \{ +		 */  	KP0 = 320,  	KP1 = 321,  	KP2 = 322, @@ -137,9 +137,9 @@ enum class Keycode {  	KP_EQUAL = 336,  	/// \}  	/** -	 * \name Modifier keys. -	 * \{ -	 */ +		 * \name Modifier keys. +		 * \{ +		 */  	LEFT_SHIFT = 340,  	LEFT_CONTROL = 341,  	LEFT_ALT = 342, @@ -151,3 +151,4 @@ enum class Keycode {  	/// \}  	MENU = 348, //!< Menu key.  }; +} // namespace crepe diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 731cfb7..dc2d9e0 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,5 +1,6 @@  #include "../system/AnimatorSystem.h"  #include "../system/CollisionSystem.h" +#include "../system/InputSystem.h"  #include "../system/ParticleSystem.h"  #include "../system/PhysicsSystem.h"  #include "../system/RenderSystem.h" @@ -20,9 +21,10 @@ LoopManager::LoopManager() {  	this->load_system<PhysicsSystem>();  	this->load_system<RenderSystem>();  	this->load_system<ScriptSystem>(); +	this->load_system<InputSystem>();  } -void LoopManager::process_input() { this->sdl_context.handle_events(this->game_running); } +void LoopManager::process_input() { this->get_system<InputSystem>().update(); }  void LoopManager::start() {  	this->setup(); diff --git a/src/crepe/api/UIObject.cpp b/src/crepe/api/UIObject.cpp new file mode 100644 index 0000000..d239b89 --- /dev/null +++ b/src/crepe/api/UIObject.cpp @@ -0,0 +1,8 @@ +#include "UIObject.h" + +using namespace crepe; + +UIObject::UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset) +	: Component(id), +	  dimensions(dimensions), +	  offset(offset) {} diff --git a/src/crepe/api/UIObject.h b/src/crepe/api/UIObject.h new file mode 100644 index 0000000..f7f4fba --- /dev/null +++ b/src/crepe/api/UIObject.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../Component.h" + +namespace crepe { + +/** + * \brief Represents a UI object in the game, derived from the Component class. + */ +class UIObject : public Component { +public: +	/** +	 * \brief Constructs a UiObject with the specified game object ID. +	 * \param id The unique ID of the game object associated with this UI object. +	 * \param dimensions width and height of the UIObject +	 * \param offset Offset relative to the GameObject Transform +	 */ +	UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset); +	//! Width and height of the UIObject +	vec2 dimensions; +	//! Position offset relative to this GameObjects Transform +	vec2 offset; +}; + +} // namespace crepe diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index e8be7ca..ad9f1f0 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -6,6 +6,7 @@  #include <SDL2/SDL_render.h>  #include <SDL2/SDL_surface.h>  #include <SDL2/SDL_video.h> +#include <array>  #include <cmath>  #include <cstddef>  #include <cstdint> @@ -18,6 +19,7 @@  #include "../api/Config.h"  #include "../api/Sprite.h"  #include "../api/Texture.h" +#include "../manager/EventManager.h"  #include "../util/Log.h"  #include "SDLContext.h" @@ -76,25 +78,141 @@ SDLContext::~SDLContext() {  	IMG_Quit();  	SDL_Quit();  } -void SDLContext::handle_events(bool & running) { -	//TODO: wouter i need events -	/* -	SDL_Event event; -	SDL_PollEvent(&event); -	switch (event.type) { -		case SDL_QUIT: -			running = false; -			break; -		case SDL_KEYDOWN: -			triggerEvent(KeyPressedEvent(getCustomKey(event.key.keysym.sym))); -			break; -		case SDL_MOUSEBUTTONDOWN: -			int x, y; -			SDL_GetMouseState(&x, &y); -			triggerEvent(MousePressedEvent(x, y)); -			break; + +Keycode SDLContext::sdl_to_keycode(SDL_Keycode sdl_key) { +	static const std::array<Keycode, SDL_NUM_SCANCODES> LOOKUP_TABLE = [] { +		std::array<Keycode, SDL_NUM_SCANCODES> table{}; +		table.fill(Keycode::NONE); + +		table[SDL_SCANCODE_SPACE] = Keycode::SPACE; +		table[SDL_SCANCODE_APOSTROPHE] = Keycode::APOSTROPHE; +		table[SDL_SCANCODE_COMMA] = Keycode::COMMA; +		table[SDL_SCANCODE_MINUS] = Keycode::MINUS; +		table[SDL_SCANCODE_PERIOD] = Keycode::PERIOD; +		table[SDL_SCANCODE_SLASH] = Keycode::SLASH; +		table[SDL_SCANCODE_0] = Keycode::D0; +		table[SDL_SCANCODE_1] = Keycode::D1; +		table[SDL_SCANCODE_2] = Keycode::D2; +		table[SDL_SCANCODE_3] = Keycode::D3; +		table[SDL_SCANCODE_4] = Keycode::D4; +		table[SDL_SCANCODE_5] = Keycode::D5; +		table[SDL_SCANCODE_6] = Keycode::D6; +		table[SDL_SCANCODE_7] = Keycode::D7; +		table[SDL_SCANCODE_8] = Keycode::D8; +		table[SDL_SCANCODE_9] = Keycode::D9; +		table[SDL_SCANCODE_SEMICOLON] = Keycode::SEMICOLON; +		table[SDL_SCANCODE_EQUALS] = Keycode::EQUAL; +		table[SDL_SCANCODE_A] = Keycode::A; +		table[SDL_SCANCODE_B] = Keycode::B; +		table[SDL_SCANCODE_C] = Keycode::C; +		table[SDL_SCANCODE_D] = Keycode::D; +		table[SDL_SCANCODE_E] = Keycode::E; +		table[SDL_SCANCODE_F] = Keycode::F; +		table[SDL_SCANCODE_G] = Keycode::G; +		table[SDL_SCANCODE_H] = Keycode::H; +		table[SDL_SCANCODE_I] = Keycode::I; +		table[SDL_SCANCODE_J] = Keycode::J; +		table[SDL_SCANCODE_K] = Keycode::K; +		table[SDL_SCANCODE_L] = Keycode::L; +		table[SDL_SCANCODE_M] = Keycode::M; +		table[SDL_SCANCODE_N] = Keycode::N; +		table[SDL_SCANCODE_O] = Keycode::O; +		table[SDL_SCANCODE_P] = Keycode::P; +		table[SDL_SCANCODE_Q] = Keycode::Q; +		table[SDL_SCANCODE_R] = Keycode::R; +		table[SDL_SCANCODE_S] = Keycode::S; +		table[SDL_SCANCODE_T] = Keycode::T; +		table[SDL_SCANCODE_U] = Keycode::U; +		table[SDL_SCANCODE_V] = Keycode::V; +		table[SDL_SCANCODE_W] = Keycode::W; +		table[SDL_SCANCODE_X] = Keycode::X; +		table[SDL_SCANCODE_Y] = Keycode::Y; +		table[SDL_SCANCODE_Z] = Keycode::Z; +		table[SDL_SCANCODE_LEFTBRACKET] = Keycode::LEFT_BRACKET; +		table[SDL_SCANCODE_BACKSLASH] = Keycode::BACKSLASH; +		table[SDL_SCANCODE_RIGHTBRACKET] = Keycode::RIGHT_BRACKET; +		table[SDL_SCANCODE_GRAVE] = Keycode::GRAVE_ACCENT; +		table[SDL_SCANCODE_ESCAPE] = Keycode::ESCAPE; +		table[SDL_SCANCODE_RETURN] = Keycode::ENTER; +		table[SDL_SCANCODE_TAB] = Keycode::TAB; +		table[SDL_SCANCODE_BACKSPACE] = Keycode::BACKSPACE; +		table[SDL_SCANCODE_INSERT] = Keycode::INSERT; +		table[SDL_SCANCODE_DELETE] = Keycode::DELETE; +		table[SDL_SCANCODE_RIGHT] = Keycode::RIGHT; +		table[SDL_SCANCODE_LEFT] = Keycode::LEFT; +		table[SDL_SCANCODE_DOWN] = Keycode::DOWN; +		table[SDL_SCANCODE_UP] = Keycode::UP; +		table[SDL_SCANCODE_PAGEUP] = Keycode::PAGE_UP; +		table[SDL_SCANCODE_PAGEDOWN] = Keycode::PAGE_DOWN; +		table[SDL_SCANCODE_HOME] = Keycode::HOME; +		table[SDL_SCANCODE_END] = Keycode::END; +		table[SDL_SCANCODE_CAPSLOCK] = Keycode::CAPS_LOCK; +		table[SDL_SCANCODE_SCROLLLOCK] = Keycode::SCROLL_LOCK; +		table[SDL_SCANCODE_NUMLOCKCLEAR] = Keycode::NUM_LOCK; +		table[SDL_SCANCODE_PRINTSCREEN] = Keycode::PRINT_SCREEN; +		table[SDL_SCANCODE_PAUSE] = Keycode::PAUSE; +		table[SDL_SCANCODE_F1] = Keycode::F1; +		table[SDL_SCANCODE_F2] = Keycode::F2; +		table[SDL_SCANCODE_F3] = Keycode::F3; +		table[SDL_SCANCODE_F4] = Keycode::F4; +		table[SDL_SCANCODE_F5] = Keycode::F5; +		table[SDL_SCANCODE_F6] = Keycode::F6; +		table[SDL_SCANCODE_F7] = Keycode::F7; +		table[SDL_SCANCODE_F8] = Keycode::F8; +		table[SDL_SCANCODE_F9] = Keycode::F9; +		table[SDL_SCANCODE_F10] = Keycode::F10; +		table[SDL_SCANCODE_F11] = Keycode::F11; +		table[SDL_SCANCODE_F12] = Keycode::F12; +		table[SDL_SCANCODE_KP_0] = Keycode::KP0; +		table[SDL_SCANCODE_KP_1] = Keycode::KP1; +		table[SDL_SCANCODE_KP_2] = Keycode::KP2; +		table[SDL_SCANCODE_KP_3] = Keycode::KP3; +		table[SDL_SCANCODE_KP_4] = Keycode::KP4; +		table[SDL_SCANCODE_KP_5] = Keycode::KP5; +		table[SDL_SCANCODE_KP_6] = Keycode::KP6; +		table[SDL_SCANCODE_KP_7] = Keycode::KP7; +		table[SDL_SCANCODE_KP_8] = Keycode::KP8; +		table[SDL_SCANCODE_KP_9] = Keycode::KP9; +		table[SDL_SCANCODE_LSHIFT] = Keycode::LEFT_SHIFT; +		table[SDL_SCANCODE_LCTRL] = Keycode::LEFT_CONTROL; +		table[SDL_SCANCODE_LALT] = Keycode::LEFT_ALT; +		table[SDL_SCANCODE_LGUI] = Keycode::LEFT_SUPER; +		table[SDL_SCANCODE_RSHIFT] = Keycode::RIGHT_SHIFT; +		table[SDL_SCANCODE_RCTRL] = Keycode::RIGHT_CONTROL; +		table[SDL_SCANCODE_RALT] = Keycode::RIGHT_ALT; +		table[SDL_SCANCODE_RGUI] = Keycode::RIGHT_SUPER; +		table[SDL_SCANCODE_MENU] = Keycode::MENU; + +		return table; +	}(); + +	if (sdl_key < 0 || sdl_key >= SDL_NUM_SCANCODES) { +		return Keycode::NONE;  	} -	*/ + +	return LOOKUP_TABLE[sdl_key]; +} + +MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) { +	static const std::array<MouseButton, 5> MOUSE_BUTTON_LOOKUP_TABLE = [] { +		std::array<MouseButton, 5> table{}; +		table.fill(MouseButton::NONE); + +		table[SDL_BUTTON_LEFT] = MouseButton::LEFT_MOUSE; +		table[SDL_BUTTON_RIGHT] = MouseButton::RIGHT_MOUSE; +		table[SDL_BUTTON_MIDDLE] = MouseButton::MIDDLE_MOUSE; +		table[SDL_BUTTON_X1] = MouseButton::X1_MOUSE; +		table[SDL_BUTTON_X2] = MouseButton::X2_MOUSE; + +		return table; +	}(); + +	if (sdl_button >= MOUSE_BUTTON_LOOKUP_TABLE.size()) { +		// Return NONE for invalid or unmapped button +		return MouseButton::NONE; +	} + +	return MOUSE_BUTTON_LOOKUP_TABLE[sdl_button];  }  void SDLContext::clear_screen() { @@ -224,6 +342,66 @@ ivec2 SDLContext::get_size(const Texture & ctx) {  void SDLContext::delay(int ms) const { SDL_Delay(ms); } +std::vector<SDLContext::EventData> SDLContext::get_events() { +	std::vector<SDLContext::EventData> event_list; +	SDL_Event event; +	while (SDL_PollEvent(&event)) { +		switch (event.type) { +			case SDL_QUIT: +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::SHUTDOWN, +				}); +				break; +			case SDL_KEYDOWN: +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::KEYDOWN, +					.key = sdl_to_keycode(event.key.keysym.scancode), +					.key_repeat = (event.key.repeat != 0), +				}); +				break; +			case SDL_KEYUP: +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::KEYUP, +					.key = sdl_to_keycode(event.key.keysym.scancode), +				}); +				break; +			case SDL_MOUSEBUTTONDOWN: +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEDOWN, +					.mouse_button = sdl_to_mousebutton(event.button.button), +					.mouse_position = {event.button.x, event.button.y}, +				}); +				break; +			case SDL_MOUSEBUTTONUP: { +				int x, y; +				SDL_GetMouseState(&x, &y); +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEUP, +					.mouse_button = sdl_to_mousebutton(event.button.button), +					.mouse_position = {event.button.x, event.button.y}, +				}); +			} break; + +			case SDL_MOUSEMOTION: { +				event_list.push_back( +					EventData{.event_type = SDLContext::EventType::MOUSEMOVE, +							  .mouse_position = {event.motion.x, event.motion.y}, +							  .rel_mouse_move = {event.motion.xrel, event.motion.yrel}}); +			} break; + +			case SDL_MOUSEWHEEL: { +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEWHEEL, +					.mouse_position = {event.motion.x, event.motion.y}, +					// TODO: why is this needed? +					.scroll_direction = event.wheel.y < 0 ? -1 : 1, +					.scroll_delta = event.wheel.preciseY, +				}); +			} break; +		} +	} +	return event_list; +}  void SDLContext::set_color_texture(const Texture & texture, const Color & color) {  	SDL_SetTextureColorMod(texture.texture.get(), color.r, color.g, color.b);  	SDL_SetTextureAlphaMod(texture.texture.get(), color.a); diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index e49ca78..a2b34c1 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -1,5 +1,6 @@  #pragma once +#include <SDL2/SDL.h>  #include <SDL2/SDL_keycode.h>  #include <SDL2/SDL_rect.h>  #include <SDL2/SDL_render.h> @@ -8,20 +9,21 @@  #include <functional>  #include <memory>  #include <string> +#include <utility> -#include "../api/Camera.h" -#include "../api/Sprite.h" - +#include "api/Camera.h"  #include "api/Color.h" +#include "api/Event.h" +#include "api/KeyCodes.h" +#include "api/Sprite.h"  #include "api/Texture.h" +#include "api/Transform.h"  #include "types.h"  namespace crepe { -// TODO: SDL_Keycode is defined in a header not distributed with crepe, which means this -// typedef is unusable when crepe is packaged. Wouter will fix this later. -typedef SDL_Keycode CREPE_KEYCODES; - +class LoopManager; +class InputSystem;  /**   * \class SDLContext   * \brief Facade for the SDL library @@ -41,6 +43,29 @@ public:  	};  public: +	//! EventType enum for passing eventType +	enum EventType { +		NONE = 0, +		MOUSEDOWN, +		MOUSEUP, +		MOUSEMOVE, +		MOUSEWHEEL, +		KEYUP, +		KEYDOWN, +		SHUTDOWN, + +	}; +	//! EventData struct for passing event data from facade +	struct EventData { +		SDLContext::EventType event_type = SDLContext::EventType::NONE; +		Keycode key = Keycode::NONE; +		bool key_repeat = false; +		MouseButton mouse_button = MouseButton::NONE; +		ivec2 mouse_position = {-1, -1}; +		int scroll_direction = -1; +		float scroll_delta = INFINITY; +		ivec2 rel_mouse_move = {-1, -1}; +	};  	/**  	 * \brief Gets the singleton instance of SDLContext.  	 * \return Reference to the SDLContext instance. @@ -53,13 +78,40 @@ public:  	SDLContext & operator=(SDLContext &&) = delete;  private: -	//! will only use handle_events -	friend class LoopManager; +	//! will only use get_events +	friend class InputSystem; +	/** +	 * \brief Retrieves a list of all events from the SDL context. +	 *  +	 * This method retrieves all the events from the SDL context that are currently +	 * available. It is primarily used by the InputSystem to process various +	 * input events such as mouse clicks, mouse movements, and keyboard presses. +	 *  +	 * \return Events that occurred since last call to `get_events()` +	 */ +	std::vector<SDLContext::EventData> get_events(); + +	/** +	 * \brief Converts an SDL key code to the custom Keycode type. +	 *  +	 * This method maps an SDL key code to the corresponding `Keycode` enum value, +	 * which is used internally by the system to identify the keys. +	 *  +	 * \param sdl_key The SDL key code to convert. +	 * \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized. +	 */ +	Keycode sdl_to_keycode(SDL_Keycode sdl_key); +  	/** -	 * \brief Handles SDL events such as window close and input. -	 * \param running Reference to a boolean flag that controls the main loop. +	 * \brief Converts an SDL mouse button code to the custom MouseButton type. +	 *  +	 * This method maps an SDL mouse button code to the corresponding `MouseButton`  +	 * enum value, which is used internally by the system to identify mouse buttons. +	 *  +	 * \param sdl_button The SDL mouse button code to convert. +	 * \return The corresponding `MouseButton` value or `MouseButton::NONE` if the key is unrecognized  	 */ -	void handle_events(bool & running); +	MouseButton sdl_to_mousebutton(Uint8 sdl_button);  private:  	//! Will only use get_ticks diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index f507b90..6b2e099 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -7,6 +7,7 @@ target_sources(crepe PUBLIC  	RenderSystem.cpp  	AudioSystem.cpp  	AnimatorSystem.cpp +	InputSystem.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -17,4 +18,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	RenderSystem.h  	AudioSystem.h  	AnimatorSystem.h +	InputSystem.h  ) diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp new file mode 100644 index 0000000..7cc8d30 --- /dev/null +++ b/src/crepe/system/InputSystem.cpp @@ -0,0 +1,187 @@ +#include "../api/Button.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" + +#include "InputSystem.h" + +using namespace crepe; + +void InputSystem::update() { +	ComponentManager & mgr = this->mediator.component_manager; +	EventManager & event_mgr = this->mediator.event_manager; +	std::vector<SDLContext::EventData> event_list = SDLContext::get_instance().get_events(); +	RefVector<Button> buttons = mgr.get_components_by_type<Button>(); +	RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); +	OptionalRef<Camera> curr_cam_ref; +	// Find the active camera +	for (Camera & cam : cameras) { +		if (!cam.active) continue; +		curr_cam_ref = cam; +		break; +	} +	if (!curr_cam_ref) return; +	Camera & current_cam = curr_cam_ref; +	RefVector<Transform> transform_vec +		= mgr.get_components_by_id<Transform>(current_cam.game_object_id); +	Transform & cam_transform = transform_vec.front().get(); +	int camera_origin_x +		= cam_transform.position.x + current_cam.offset.x - (current_cam.viewport_size.x / 2); +	int camera_origin_y +		= cam_transform.position.y + current_cam.offset.y - (current_cam.viewport_size.y / 2); + +	for (const SDLContext::EventData & event : event_list) { +		int world_mouse_x = event.mouse_position.x + camera_origin_x; +		int world_mouse_y = event.mouse_position.y + camera_origin_y; +		// check if the mouse is within the viewport +		bool mouse_in_viewport +			= !(world_mouse_x < camera_origin_x +				|| world_mouse_x > camera_origin_x + current_cam.viewport_size.x +				|| world_mouse_y < camera_origin_y +				|| world_mouse_y > camera_origin_y + current_cam.viewport_size.y); + +		switch (event.event_type) { +			case SDLContext::EventType::KEYDOWN: +				event_mgr.queue_event<KeyPressEvent>(KeyPressEvent{ +					.repeat = event.key_repeat, +					.key = event.key, +				}); +				break; +			case SDLContext::EventType::KEYUP: +				event_mgr.queue_event<KeyReleaseEvent>(KeyReleaseEvent{ +					.key = event.key, +				}); +				break; +			case SDLContext::EventType::MOUSEDOWN: +				if (!mouse_in_viewport) { +					break; +				} +				event_mgr.queue_event<MousePressEvent>(MousePressEvent{ +					.mouse_x = world_mouse_x, +					.mouse_y = world_mouse_y, +					.button = event.mouse_button, +				}); +				this->last_mouse_down_position = {world_mouse_x, world_mouse_y}; +				this->last_mouse_button = event.mouse_button; +				break; +			case SDLContext::EventType::MOUSEUP: { +				if (!mouse_in_viewport) { +					break; +				} +				event_mgr.queue_event<MouseReleaseEvent>(MouseReleaseEvent{ +					.mouse_x = world_mouse_x, +					.mouse_y = world_mouse_y, +					.button = event.mouse_button, +				}); +				//check if its a click by checking the last button down +				int delta_x = world_mouse_x - this->last_mouse_down_position.x; +				int delta_y = world_mouse_y - this->last_mouse_down_position.y; + +				if (this->last_mouse_button == event.mouse_button +					&& std::abs(delta_x) <= click_tolerance +					&& std::abs(delta_y) <= click_tolerance) { +					event_mgr.queue_event<MouseClickEvent>(MouseClickEvent{ +						.mouse_x = world_mouse_x, +						.mouse_y = world_mouse_y, +						.button = event.mouse_button, +					}); + +					this->handle_click(event.mouse_button, world_mouse_x, world_mouse_y); +				} +			} break; +			case SDLContext::EventType::MOUSEMOVE: +				if (!mouse_in_viewport) { +					break; +				} +				event_mgr.queue_event<MouseMoveEvent>(MouseMoveEvent{ +					.mouse_x = world_mouse_x, +					.mouse_y = world_mouse_y, +					.delta_x = event.rel_mouse_move.x, +					.delta_y = event.rel_mouse_move.y, +				}); +				this->handle_move(event, world_mouse_x, world_mouse_y); +				break; +			case SDLContext::EventType::MOUSEWHEEL: +				event_mgr.queue_event<MouseScrollEvent>(MouseScrollEvent{ +					.mouse_x = world_mouse_x, +					.mouse_y = world_mouse_y, +					.scroll_direction = event.scroll_direction, +					.scroll_delta = event.scroll_delta, +				}); +				break; +			case SDLContext::EventType::SHUTDOWN: +				event_mgr.queue_event<ShutDownEvent>(ShutDownEvent{}); +				break; +			default: +				break; +		} +	} +} +void InputSystem::handle_move(const SDLContext::EventData & event_data, +							  const int world_mouse_x, const int world_mouse_y) { +	ComponentManager & mgr = this->mediator.component_manager; + +	RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + +	for (Button & button : buttons) { +		RefVector<Transform> transform_vec +			= mgr.get_components_by_id<Transform>(button.game_object_id); +		Transform & transform(transform_vec.front().get()); + +		bool was_hovering = button.hover; +		if (button.active +			&& this->is_mouse_inside_button(world_mouse_x, world_mouse_y, button, transform)) { +			button.hover = true; +			if (!was_hovering && button.on_mouse_enter) { +				button.on_mouse_enter(); +			} +		} else { +			button.hover = false; +			// Trigger the on_exit callback if the hover state just changed to false +			if (was_hovering && button.on_mouse_exit) { +				button.on_mouse_exit(); +			} +		} +	} +} + +void InputSystem::handle_click(const MouseButton & mouse_button, const int world_mouse_x, +							   const int world_mouse_y) { +	ComponentManager & mgr = this->mediator.component_manager; + +	RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + +	for (Button & button : buttons) { +		RefVector<Transform> transform_vec +			= mgr.get_components_by_id<Transform>(button.game_object_id); +		Transform & transform = transform_vec.front().get(); + +		if (button.active +			&& this->is_mouse_inside_button(world_mouse_x, world_mouse_y, button, transform)) { +			this->handle_button_press(button); +		} +	} +} + +bool InputSystem::is_mouse_inside_button(const int mouse_x, const int mouse_y, +										 const Button & button, const Transform & transform) { +	int actual_x = transform.position.x + button.offset.x; +	int actual_y = transform.position.y + button.offset.y; + +	int half_width = button.dimensions.x / 2; +	int half_height = button.dimensions.y / 2; + +	// Check if the mouse is within the button's boundaries +	return mouse_x >= actual_x - half_width && mouse_x <= actual_x + half_width +		   && mouse_y >= actual_y - half_height && mouse_y <= actual_y + half_height; +} + +void InputSystem::handle_button_press(Button & button) { +	if (button.is_toggle) { +		if (!button.is_pressed && button.on_click) { +			button.on_click(); +		} +		button.is_pressed = !button.is_pressed; +	} else if (button.on_click) { +		button.on_click(); +	} +} diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h new file mode 100644 index 0000000..87e86f8 --- /dev/null +++ b/src/crepe/system/InputSystem.h @@ -0,0 +1,85 @@ +#pragma once + +#include "../facade/SDLContext.h" +#include "../types.h" +#include "../util/OptionalRef.h" + +#include "System.h" + +namespace crepe { + +class Camera; +class Button; +class Transform; + +/** + * \brief Handles the processing of input events created by SDLContext + * + * This system processes events such as mouse clicks, mouse movement, and keyboard + * actions. It is responsible for detecting interactions with UI buttons and + * passing the corresponding events to the registered listeners. + */ +class InputSystem : public System { +public: +	using System::System; + +	/** +	 * \brief Updates the system, processing all input events. +	 * This method processes all events and triggers corresponding actions. +	 */ +	void update() override; + +private: +	//! Stores the last position of the mouse when the button was pressed. +	ivec2 last_mouse_down_position; +	// TODO: specify world/hud space and make regular `vec2` + +	//! Stores the last mouse button pressed. +	MouseButton last_mouse_button = MouseButton::NONE; + +	//! The maximum allowable distance between mouse down and mouse up to register as a click. +	const int click_tolerance = 5; + +	/** +	* \brief Handles the mouse click event. +	* \param mouse_button The mouse button involved in the click. +	* \param world_mouse_x The X coordinate of the mouse in world space. +	* \param world_mouse_y The Y coordinate of the mouse in world space. +	* +	* This method processes the mouse click event and triggers the corresponding button action. +	*/ +	void handle_click(const MouseButton & mouse_button, const int world_mouse_x, +					  const int world_mouse_y); + +	/** +	* \brief Handles the mouse movement event. +	* \param event_data The event data containing information about the mouse movement. +	* \param world_mouse_x The X coordinate of the mouse in world space. +	* \param world_mouse_y The Y coordinate of the mouse in world space. +	* +	* This method processes the mouse movement event and updates the button hover state. +	*/ +	void handle_move(const SDLContext::EventData & event_data, const int world_mouse_x, +					 const int world_mouse_y); + +	/** +	* \brief Checks if the mouse position is inside the bounds of the button. +	* \param world_mouse_x The X coordinate of the mouse in world space. +	* \param world_mouse_y The Y coordinate of the mouse in world space. +	* \param button The button to check. +	* \param transform The transform component of the button. +	* \return True if the mouse is inside the button, false otherwise. +	*/ +	bool is_mouse_inside_button(const int world_mouse_x, const int world_mouse_y, +								const Button & button, const Transform & transform); + +	/** +	* \brief Handles the button press event, calling the on_click callback if necessary. +	* \param button The button being pressed. +	* +	* This method triggers the on_click action for the button when it is pressed. +	*/ +	void handle_button_press(Button & button); +}; + +} // namespace crepe diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 9c3c550..b8ca309 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -18,5 +18,4 @@ endfunction()  add_example(savemgr)  add_example(rendering_particle) -add_example(gameloop) - +add_example(button) diff --git a/src/example/button.cpp b/src/example/button.cpp new file mode 100644 index 0000000..00bdc28 --- /dev/null +++ b/src/example/button.cpp @@ -0,0 +1,54 @@ +#include <SDL2/SDL_timer.h> +#include <chrono> +#include <crepe/Component.h> +#include <crepe/ComponentManager.h> +#include <crepe/api/Animator.h> +#include <crepe/api/Button.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/EventManager.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> +#include <crepe/system/AnimatorSystem.h> +#include <crepe/system/InputSystem.h> +#include <crepe/system/RenderSystem.h> +#include <crepe/types.h> +#include <iostream> +using namespace crepe; +using namespace std; + +int main(int argc, char * argv[]) { +	ComponentManager mgr; +	RenderSystem sys{mgr}; +	EventManager & event_mgr = EventManager::get_instance(); +	InputSystem input_sys{mgr}; +	AnimatorSystem asys{mgr}; +	GameObject camera_obj = mgr.new_object("", "", vec2{1000, 1000}, 0, 1); +	camera_obj.add_component<Camera>(Color::WHITE, ivec2{1080, 720}, vec2{2000, 2000}, 1.0f); + +	GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	auto s2 = Texture("asset/texture/test_ap43.png"); +	bool button_clicked = false; +	auto & sprite2 = button_obj.add_component<Sprite>( +		s2, Color::GREEN, Sprite::FlipSettings{false, false}, 2, 1, 100); +	std::function<void()> on_click = [&]() { std::cout << "button clicked" << std::endl; }; +	std::function<void()> on_enter = [&]() { std::cout << "enter" << std::endl; }; +	std::function<void()> on_exit = [&]() { std::cout << "exit" << std::endl; }; +	auto & button +		= button_obj.add_component<Button>(vec2{100, 100}, vec2{0, 0}, on_click, false); +	button.on_mouse_enter = on_enter; +	button.on_mouse_exit = on_exit; +	button.is_toggle = true; +	button.active = true; +	auto start = std::chrono::steady_clock::now(); +	while (true) { +		input_sys.update(); +		sys.update(); +		asys.update(); +		event_mgr.dispatch_events(); +		SDL_Delay(30); +	} +	return 0; +} diff --git a/src/example/gameloop.cpp b/src/example/gameloop.cpp deleted file mode 100644 index a676f20..0000000 --- a/src/example/gameloop.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "crepe/api/LoopManager.h" -using namespace crepe; -int main() { -	LoopManager gameloop; -	gameloop.start(); -	return 1; -} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 4174926..ce529b1 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources(test_main PUBLIC  	ValueBrokerTest.cpp  	DBTest.cpp  	Vector2Test.cpp +	InputTest.cpp  	ScriptEventTest.cpp  	ScriptSceneTest.cpp  ) diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp index dccd554..4a4872d 100644 --- a/src/test/EventTest.cpp +++ b/src/test/EventTest.cpp @@ -156,29 +156,37 @@ TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) {  	bool triggered1 = false;  	bool triggered2 = false;  	int test_channel = 1; -	EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { + +	// Adjusted to use KeyPressEvent with repeat as the first variable +	EventHandler<KeyPressEvent> key_handler1 = [&](const KeyPressEvent & e) {  		triggered1 = true; -		EXPECT_EQ(e.mouse_x, 100); -		EXPECT_EQ(e.mouse_y, 200); -		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		EXPECT_EQ(e.repeat, false); // Expecting repeat to be false +		EXPECT_EQ(e.key, Keycode::A); // Adjust expected key code  		return false; // Allows propagation  	}; -	EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { + +	EventHandler<KeyPressEvent> key_handler2 = [&](const KeyPressEvent & e) {  		triggered2 = true; -		EXPECT_EQ(e.mouse_x, 100); -		EXPECT_EQ(e.mouse_y, 200); -		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		EXPECT_EQ(e.repeat, false); // Expecting repeat to be false +		EXPECT_EQ(e.key, Keycode::A); // Adjust expected key code  		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}, +	// Subscribe handlers to KeyPressEvent +	event_manager.subscribe<KeyPressEvent>(key_handler1); +	event_manager.subscribe<KeyPressEvent>(key_handler2, test_channel); + +	// Queue a KeyPressEvent instead of KeyDownEvent +	event_manager.queue_event<KeyPressEvent>(KeyPressEvent{ +		.repeat = false, .key = Keycode::A}); // Adjust event with repeat flag first + +	event_manager.queue_event<KeyPressEvent>( +		KeyPressEvent{.repeat = false, +					  .key = Keycode::A}, // Adjust event for second subscription  		test_channel); +  	event_manager.dispatch_events(); +  	EXPECT_TRUE(triggered1);  	EXPECT_TRUE(triggered2);  } diff --git a/src/test/InputTest.cpp b/src/test/InputTest.cpp new file mode 100644 index 0000000..cb9833f --- /dev/null +++ b/src/test/InputTest.cpp @@ -0,0 +1,293 @@ +#include <gtest/gtest.h> +#define protected public +#define private public +#include "api/KeyCodes.h" +#include "manager/ComponentManager.h" +#include "manager/EventManager.h" +#include "manager/Mediator.h" +#include "system/InputSystem.h" +#include <SDL2/SDL.h> +#include <SDL2/SDL_keycode.h> +#include <crepe/api/Button.h> +#include <crepe/api/Camera.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <gmock/gmock.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class InputTest : public ::testing::Test { +public: +	Mediator mediator; +	ComponentManager mgr{mediator}; + +	InputSystem input_system{mediator}; + +	EventManager & event_manager = EventManager::get_instance(); +	//GameObject camera; + +protected: +	void SetUp() override { +		mediator.event_manager = event_manager; +		mediator.component_manager = mgr; +		event_manager.clear(); +	} + +	void simulate_mouse_click(int mouse_x, int mouse_y, Uint8 mouse_button) { +		SDL_Event event; + +		// Simulate Mouse Button Down event +		SDL_zero(event); +		event.type = SDL_MOUSEBUTTONDOWN; +		event.button.x = mouse_x; +		event.button.y = mouse_y; +		event.button.button = mouse_button; +		SDL_PushEvent(&event); + +		// Simulate Mouse Button Up event +		SDL_zero(event); +		event.type = SDL_MOUSEBUTTONUP; +		event.button.x = mouse_x; +		event.button.y = mouse_y; +		event.button.button = mouse_button; +		SDL_PushEvent(&event); +	} +}; + +TEST_F(InputTest, MouseDown) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, +											  vec2{0, 0}); +	camera.active = true; +	bool mouse_triggered = false; +	EventHandler<MousePressEvent> on_mouse_down = [&](const MousePressEvent & event) { +		mouse_triggered = true; +		//middle of the screen = 0,0 +		EXPECT_EQ(event.mouse_x, 0); +		EXPECT_EQ(event.mouse_y, 0); +		EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE); +		return false; +	}; +	event_manager.subscribe<MousePressEvent>(on_mouse_down); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEBUTTONDOWN; +	// middle of the screen of a 500*500 camera = 250*250 +	event.button.x = 250; +	event.button.y = 250; +	event.button.button = SDL_BUTTON_LEFT; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(mouse_triggered); +} + +TEST_F(InputTest, MouseUp) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, +											  vec2{0, 0}); +	camera.active = true; +	bool function_triggered = false; +	EventHandler<MouseReleaseEvent> on_mouse_release = [&](const MouseReleaseEvent & e) { +		function_triggered = true; +		EXPECT_EQ(e.mouse_x, 0); +		EXPECT_EQ(e.mouse_y, 0); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; +	}; +	event_manager.subscribe<MouseReleaseEvent>(on_mouse_release); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEBUTTONUP; +	event.button.x = 250; +	event.button.y = 250; +	event.button.button = SDL_BUTTON_LEFT; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseMove) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, +											  vec2{0, 0}); +	camera.active = true; +	bool function_triggered = false; +	EventHandler<MouseMoveEvent> on_mouse_move = [&](const MouseMoveEvent & e) { +		function_triggered = true; +		EXPECT_EQ(e.mouse_x, 0); +		EXPECT_EQ(e.mouse_y, 0); +		EXPECT_EQ(e.delta_x, 10); +		EXPECT_EQ(e.delta_y, 10); +		return false; +	}; +	event_manager.subscribe<MouseMoveEvent>(on_mouse_move); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEMOTION; +	event.motion.x = 250; +	event.motion.y = 250; +	event.motion.xrel = 10; +	event.motion.yrel = 10; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, KeyDown) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, +											  vec2{0, 0}); +	camera.active = true; +	bool function_triggered = false; + +	// Define event handler for KeyPressEvent +	EventHandler<KeyPressEvent> on_key_press = [&](const KeyPressEvent & event) { +		function_triggered = true; +		EXPECT_EQ(event.key, Keycode::B); // Validate the key is 'B' +		EXPECT_EQ(event.repeat, true); // Validate repeat flag +		return false; +	}; + +	event_manager.subscribe<KeyPressEvent>(on_key_press); + +	// Simulate SDL_KEYDOWN event +	SDL_Event test_event; +	SDL_zero(test_event); +	test_event.type = SDL_KEYDOWN; // Key down event +	test_event.key.keysym.scancode = SDL_SCANCODE_B; // Set scancode for 'B' +	test_event.key.repeat = 1; // Set repeat flag +	SDL_PushEvent(&test_event); + +	input_system.update(); // Process the event +	event_manager.dispatch_events(); // Dispatch events to handlers + +	EXPECT_TRUE(function_triggered); // Check if the handler was triggered +} + +TEST_F(InputTest, KeyUp) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, +											  vec2{0, 0}); +	camera.active = true; +	bool function_triggered = false; +	EventHandler<KeyReleaseEvent> on_key_release = [&](const KeyReleaseEvent & event) { +		function_triggered = true; +		EXPECT_EQ(event.key, Keycode::B); +		return false; +	}; +	event_manager.subscribe<KeyReleaseEvent>(on_key_release); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_KEYUP; +	event.key.keysym.scancode = SDL_SCANCODE_B; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseClick) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, +											  vec2{0, 0}); +	camera.active = true; +	bool on_click_triggered = false; +	EventHandler<MouseClickEvent> on_mouse_click = [&](const MouseClickEvent & event) { +		on_click_triggered = true; +		EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE); +		EXPECT_EQ(event.mouse_x, 0); +		EXPECT_EQ(event.mouse_y, 0); +		return false; +	}; +	event_manager.subscribe<MouseClickEvent>(on_mouse_click); + +	this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(on_click_triggered); +} + +TEST_F(InputTest, testButtonClick) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, +											  vec2{0, 0}); +	camera.active = true; +	GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	bool button_clicked = false; +	std::function<void()> on_click = [&]() { button_clicked = true; }; +	auto & button +		= button_obj.add_component<Button>(vec2{100, 100}, vec2{0, 0}, on_click, false); + +	bool hover = false; +	button.active = true; + +	button.is_pressed = false; +	button.is_toggle = false; +	this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_FALSE(button_clicked); + +	this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(button_clicked); +} + +TEST_F(InputTest, testButtonHover) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, +											  vec2{0, 0}); +	camera.active = true; +	GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	bool button_clicked = false; +	std::function<void()> on_click = [&]() { button_clicked = true; }; +	auto & button +		= button_obj.add_component<Button>(vec2{100, 100}, vec2{0, 0}, on_click, false); +	button.active = true; +	button.is_pressed = false; +	button.is_toggle = false; + +	// Mouse not on button +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEMOTION; +	event.motion.x = 700; +	event.motion.y = 700; +	event.motion.xrel = 10; +	event.motion.yrel = 10; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_FALSE(button.hover); + +	// Mouse on button +	SDL_Event hover_event; +	SDL_zero(hover_event); +	hover_event.type = SDL_MOUSEMOTION; +	hover_event.motion.x = 250; +	hover_event.motion.y = 250; +	hover_event.motion.xrel = 10; +	hover_event.motion.yrel = 10; +	SDL_PushEvent(&hover_event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(button.hover); +} |