diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | mwe/events/include/event.h | 2 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/crepe/api/Button.cpp | 11 | ||||
| -rw-r--r-- | src/crepe/api/Button.h | 89 | ||||
| -rw-r--r-- | src/crepe/api/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | src/crepe/api/Event.h | 20 | ||||
| -rw-r--r-- | src/crepe/api/Font.cpp | 45 | ||||
| -rw-r--r-- | src/crepe/api/Font.h | 60 | ||||
| -rw-r--r-- | src/crepe/api/KeyCodes.h | 21 | ||||
| -rw-r--r-- | src/crepe/api/LoopManager.cpp | 6 | ||||
| -rw-r--r-- | src/crepe/api/Text.h | 42 | ||||
| -rw-r--r-- | src/crepe/api/UiObject.cpp | 8 | ||||
| -rw-r--r-- | src/crepe/api/UiObject.h | 33 | ||||
| -rw-r--r-- | src/crepe/facade/SDLContext.cpp | 162 | ||||
| -rw-r--r-- | src/crepe/facade/SDLContext.h | 78 | ||||
| -rw-r--r-- | src/crepe/system/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/crepe/system/InputSystem.cpp | 147 | ||||
| -rw-r--r-- | src/crepe/system/InputSystem.h | 76 | ||||
| -rw-r--r-- | src/example/CMakeLists.txt | 1 | ||||
| -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 | 250 | 
24 files changed, 1031 insertions, 76 deletions
| @@ -16,3 +16,4 @@ CTestTestfile.cmake  _deps  CMakeUserPresets.json  compile.sh +asset diff --git a/mwe/events/include/event.h b/mwe/events/include/event.h index ee1bf52..e1b220b 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/CMakeLists.txt b/src/CMakeLists.txt index c3f29da..b0b034f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,7 @@ project(crepe C CXX)  find_package(SDL2 REQUIRED)  find_package(SDL2_image REQUIRED) +find_package(SDL2_ttf REQUIRED)  find_package(SoLoud REQUIRED)  find_package(GTest REQUIRED)  find_package(whereami REQUIRED) @@ -24,6 +25,7 @@ target_include_directories(crepe  target_link_libraries(crepe  	PRIVATE soloud  	PUBLIC SDL2 +	PUBLIC SDL_ttf  	PUBLIC SDL2_image  	PUBLIC ${BERKELEY_DB}  	PUBLIC whereami diff --git a/src/crepe/api/Button.cpp b/src/crepe/api/Button.cpp new file mode 100644 index 0000000..d325014 --- /dev/null +++ b/src/crepe/api/Button.cpp @@ -0,0 +1,11 @@ +#include "Button.h" + +namespace crepe { + +Button::Button(game_object_id_t id, int width, int height, std::function<void()> on_click, +			   bool is_toggle) +	: UiObject(id, width, height), +	  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..0dfad5d --- /dev/null +++ b/src/crepe/api/Button.h @@ -0,0 +1,89 @@ +#pragma once + +#include <functional> + +#include "UiObject.h" + + +namespace crepe { + +/** + * \class Button + * \brief Represents a clickable UI button, derived from the UiObject class. + *  + * This class provides functionality for a button in the UI, including toggle state, + * click handling, and mouse hover detection. A callback function can be provided to + * handle button clicks. + */ +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 width The width of the button. +     * \param height The height of the button. +     * \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, int width, int height, 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; + +	/** +     * \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; + +	/** +	 * \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_enter; + +	/** +	 * \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_exit; + +private: +	friend class InputSystem; +	/** +     * \brief Indicates whether the button is currently pressed. +     *  +     * This state is true when the button is actively pressed and false otherwise. +     */ +	bool is_pressed = false; + +	/** +     * \brief Indicates whether the mouse is currently hovering over the button. +     *  +     * This is set to true when the mouse is over the button and false otherwise. +     */ +	bool hover = false; + +public: +	/** +     * \brief Retrieves the maximum number of instances allowed for this button type. +     *  +     * \return Always returns 1, as only a single instance of this type is allowed. +     */ +	virtual int get_instances_max() const override { return 1; } +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 50c51ed..2d86968 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -23,6 +23,9 @@ target_sources(crepe PUBLIC  	Asset.cpp  	EventHandler.cpp  	Script.cpp +	Button.cpp +	UiObject.cpp +	Font.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -58,4 +61,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	LoopManager.h  	LoopTimer.h  	Asset.h +	Button.h +	UiObject.h +	Font.h +	Text.h  ) diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index b267e3e..91a30b5 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -88,9 +88,29 @@ public:  	//! Y-coordinate of the mouse position at the time of the event.  	int mouse_y = 0; + +	// Movement since last event in x +	int rel_x = 0; + +	// Movement since last event in y +	int rel_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 scroll_x = 0; + +	//! Y-coordinate of the mouse position at the time of the event. +	int scroll_y = 0; + +	//! scroll direction (-1 = down, 1 = up) +	int direction = 0; +}; +/**   * \brief Event triggered during a collision between objects.   */  class CollisionEvent : public Event {}; diff --git a/src/crepe/api/Font.cpp b/src/crepe/api/Font.cpp new file mode 100644 index 0000000..8db4b23 --- /dev/null +++ b/src/crepe/api/Font.cpp @@ -0,0 +1,45 @@ +#include <SDL2/SDL_ttf.h> + +#include "facade/SDLContext.h" +#include "util/Log.h" + +#include "Asset.h" +#include "Font.h" + +using namespace crepe; +using namespace std; + +Font::Font(const Asset &src, int size) : font_size(size) { +    dbg_trace(); +    this->load(src, size); +} + +Font::~Font() { +    dbg_trace(); +    this->font.reset(); +} + +void Font::load(const Asset &res, int size) { +    SDLContext &ctx = SDLContext::get_instance(); +    // Open the font using SDL's TTF_OpenFontRW, which supports loading from memory +    SDL_RWops *rw_ops = SDL_RWFromFile(res.get_path().c_str(), "rb"); +    if (!rw_ops) { +        // dbg_log("Failed to create RWops for font: %s", SDL_GetError()); +        return; +    } + +    TTF_Font *loaded_font = TTF_OpenFontRW(rw_ops, 1, size); // 1 indicates SDL should free the RWops +    if (!loaded_font) { +        // dbg_log("Failed to load font from asset: %s", TTF_GetError()); +        return; +    } + +    // Wrap the TTF_Font with a unique_ptr for automatic cleanup +    this->font = unique_ptr<TTF_Font, function<void(TTF_Font *)>>(loaded_font, [](TTF_Font *f) { +        if (f) TTF_CloseFont(f); +    }); +} + +int Font::get_size() const { +    return this->font_size; +} diff --git a/src/crepe/api/Font.h b/src/crepe/api/Font.h new file mode 100644 index 0000000..012c271 --- /dev/null +++ b/src/crepe/api/Font.h @@ -0,0 +1,60 @@ +#pragma once + +#include <SDL2/SDL_ttf.h> +#include <functional> +#include <memory> + +#include "Asset.h" + +namespace crepe { + +class SDLContext; + +/** + * \class Font + * \brief Manages font loading and text rendering properties. + * + * The Font class is responsible for loading font resources and providing a way to render text + * with different styles and sizes. It can be used for text rendering in the game engine. + */ +class Font { + +public: +    /** +     * \brief Constructs a Font from an Asset resource. +     * \param src Asset with font data to load. +     * \param size The point size to render the font at. +     */ +    Font(const Asset &src, int size); + +    /** +     * \brief Destroys the Font instance, freeing associated resources. +     */ +    ~Font(); + +    /** +     * \brief Gets the size of the font. +     * \return The point size of the font. +     */ +    int get_size() const; + +private: +    /** +     * \brief Loads the font from an Asset resource. +     * \param res The Asset resource containing the font data. +     * \param size The point size to render the font at. +     */ +    void load(const Asset &res, int size); + +private: +    //! The font resource from the SDL_ttf library. +    std::unique_ptr<TTF_Font, std::function<void(TTF_Font *)>> font; + +    //! The size of the font in points. +    int font_size; + +    //! Grants SDLContext access to private members. +    friend class SDLContext; +}; + +} // namespace crepe 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 7edf4d1..b343250 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -2,6 +2,7 @@  #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,11 +21,10 @@ LoopManager::LoopManager() {  	this->load_system<PhysicsSystem>();  	this->load_system<RenderSystem>();  	this->load_system<ScriptSystem>(); +	this->load_system<InputSystem>();  } -void LoopManager::process_input() { -	SDLContext::get_instance().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/Text.h b/src/crepe/api/Text.h new file mode 100644 index 0000000..6bb011c --- /dev/null +++ b/src/crepe/api/Text.h @@ -0,0 +1,42 @@ +#pragma once + +#include <string> + +#include "Color.h" +#include "UiObject.h" +#include "" +namespace crepe { + +/** + * \class Button + * \brief Represents a clickable UI button, derived from the UiObject class. + *  + * This class provides functionality for a button in the UI, including toggle state, + * click handling, and mouse hover detection. A callback function can be provided to + * handle button clicks. + */ +class Text : 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 width The width of the button. +     * \param height The height of the button. +     */ +	Text(game_object_id_t id, int width, int height); + +	Color color = Color{0,0,0,0}; +	std::string text = ""; +	int size = 0; +	const std::shared_ptr<Font> sprite_image; +public: +	/** +     * \brief Retrieves the maximum number of instances allowed for this button type. +     *  +     * \return Always returns 1, as only a single instance of this type is allowed. +     */ +	virtual int get_instances_max() const override { return 10; } +}; + +} // namespace crepe diff --git a/src/crepe/api/UiObject.cpp b/src/crepe/api/UiObject.cpp new file mode 100644 index 0000000..7859a90 --- /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, int width, int height) +	: Component(id), +	  width(width), +	  height(height){}; diff --git a/src/crepe/api/UiObject.h b/src/crepe/api/UiObject.h new file mode 100644 index 0000000..c056877 --- /dev/null +++ b/src/crepe/api/UiObject.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../Component.h" + +namespace crepe { + +/** + * @class UiObject + * \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. +     */ +	UiObject(game_object_id_t id, int width, int height); + +	//! The width of the UI object. +	int width = 0; + +	//! The height of the UI object. +	int height = 0; + +public: +	/** +     * \brief Retrieves the maximum number of instances allowed for this UI object type. +     * /return Always returns 1, as only a single instance is allowed. +     */ +	virtual int get_instances_max() const override { return 1; } +}; + +} // namespace crepe diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index e8be7ca..9b4595e 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> @@ -15,7 +16,6 @@  #include <stdexcept>  #include "../api/Camera.h" -#include "../api/Config.h"  #include "../api/Sprite.h"  #include "../api/Texture.h"  #include "../util/Log.h" @@ -76,31 +76,144 @@ 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];  } -void SDLContext::clear_screen() { -	SDL_SetRenderDrawColor(this->game_renderer.get(), 0, 0, 0, 255); -	SDL_RenderClear(this->game_renderer.get()); +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() { SDL_RenderClear(this->game_renderer.get()); }  void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); }  SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { @@ -223,8 +336,3 @@ ivec2 SDLContext::get_size(const Texture & ctx) {  }  void SDLContext::delay(int ms) const { SDL_Delay(ms); } - -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..a56232f 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,22 @@  #include <functional>  #include <memory>  #include <string> - -#include "../api/Camera.h" -#include "../api/Sprite.h" - +#include <utility> + +#include "api/Camera.h" +#include "api/Event.h" +#include "api/KeyCodes.h" +#include "api/Sprite.h" +#include "api/Transform.h" +#include "api/Camera.h"  #include "api/Color.h"  #include "api/Texture.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 +44,28 @@ 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; +		std::pair<int, int> mouse_position = {-1, -1}; +		int wheel_delta = -1; +		std::pair<int, int> 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 A vector of `SDLContext::EventData` containing the 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. +	 */ +	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.  	 */ -	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 d658b25..95f6e33 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(crepe PUBLIC  	CollisionSystem.cpp  	RenderSystem.cpp  	AnimatorSystem.cpp +	InputSystem.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -15,4 +16,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	CollisionSystem.h  	RenderSystem.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..beeef87 --- /dev/null +++ b/src/crepe/system/InputSystem.cpp @@ -0,0 +1,147 @@ +#include "ComponentManager.h" +#include "api/Button.h" +#include "api/EventManager.h" + +#include "InputSystem.h" + +using namespace crepe; + +void InputSystem::update() { +	EventManager & event_mgr = EventManager::get_instance(); +	std::vector<SDLContext::EventData> event_list = SDLContext::get_instance().get_events(); + +	for (const SDLContext::EventData & event : event_list) { +		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: +				event_mgr.queue_event<MousePressEvent>(MousePressEvent{ +					.mouse_x = event.mouse_position.first, +					.mouse_y = event.mouse_position.second, +					.button = event.mouse_button, +				}); +				last_mouse_down_position = event.mouse_position; +				last_mouse_button = event.mouse_button; +				break; +			case SDLContext::EventType::MOUSEUP: { +				event_mgr.queue_event<MouseReleaseEvent>(MouseReleaseEvent{ +					.mouse_x = event.mouse_position.first, +					.mouse_y = event.mouse_position.second, +					.button = event.mouse_button, +				}); + +				int delta_x = event.mouse_position.first - last_mouse_down_position.first; +				int delta_y = event.mouse_position.second - last_mouse_down_position.second; + +				if (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 = event.mouse_position.first, +						.mouse_y = event.mouse_position.second, +						.button = event.mouse_button, +					}); + +					handle_click(event); +				} +			} break; +			case SDLContext::EventType::MOUSEMOVE: +				event_mgr.queue_event<MouseMoveEvent>(MouseMoveEvent{ +					.mouse_x = event.mouse_position.first, +					.mouse_y = event.mouse_position.second, +					.rel_x = event.rel_mouse_move.first, +					.rel_y = event.rel_mouse_move.second, +				}); +				handle_move(event); +				break; +			case SDLContext::EventType::MOUSEWHEEL: +				event_mgr.queue_event<MouseScrollEvent>(MouseScrollEvent{ +					.scroll_x = event.wheel_delta, +					.scroll_y = 0, +					.direction = event.wheel_delta, +				}); +				break; +			case SDLContext::EventType::SHUTDOWN: +				event_mgr.queue_event<ShutDownEvent>(ShutDownEvent{}); +				break; +			default: +				break; +		} +	} +} + +void InputSystem::handle_move(const SDLContext::EventData & event_data) { +	ComponentManager & mgr = this->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); +		OptionalRef<Transform> transform(transform_vec.front().get()); +		if (!transform) continue; + +		bool was_hovering = button.hover; // Store previous hover state + +		// Check if the mouse is inside the button +		if (button.active && is_mouse_inside_button(event_data, button, transform)) { +			button.hover = true; + +			// Trigger the on_enter callback if the hover state just changed to true +			if (!was_hovering && button.on_enter) { +				button.on_enter(); +			} +		} else { +			button.hover = false; + +			// Trigger the on_exit callback if the hover state just changed to false +			if (was_hovering && button.on_exit) { +				button.on_exit(); +			} +		} +	} +} + +void InputSystem::handle_click(const SDLContext::EventData & event_data) { +	ComponentManager & mgr = this->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); +		OptionalRef<Transform> transform(transform_vec.front().get()); + +		if (button.active && is_mouse_inside_button(event_data, button, transform)) { +			handle_button_press(button); +		} +	} +} + +bool InputSystem::is_mouse_inside_button(const SDLContext::EventData & event_data, +										 const Button & button, const Transform & transform) { +	return event_data.mouse_position.first >= transform.position.x +		   && event_data.mouse_position.first <= transform.position.x + button.width +		   && event_data.mouse_position.second >= transform.position.y +		   && event_data.mouse_position.second <= transform.position.y + button.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..c6ca114 --- /dev/null +++ b/src/crepe/system/InputSystem.h @@ -0,0 +1,76 @@ +#pragma once + +#include "facade/SDLContext.h" +#include "types.h" +#include "util/OptionalRef.h" + +#include "System.h" + +namespace crepe { + +class Button; + +class Transform; +/** + * \class InputSystem + * \brief Handles the processing of input events like mouse and keyboard interactions. + *  + * 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. +	std::pair<int, int> last_mouse_down_position{-1, -1}; + +	//! Stores the last mouse button pressed. +	MouseButton last_mouse_button = MouseButton::NONE; + +	//! The tolerance in game units for detecting a mouse click. +	const int click_tolerance = 5; + +	/** +     * \brief Handles the click event. +     * \param eventData The event data containing information about the mouse click. +     *  +     * This method processes the mouse click event and triggers the corresponding button action. +     */ +	void handle_click(const SDLContext::EventData & eventData); + +	/** +     * \brief Handles the mouse movement event. +     * \param eventData The event data containing information about the mouse movement. +     *  +     * This method processes the mouse movement event and updates the button hover state. +     */ +	void handle_move(const SDLContext::EventData & eventData); +	/** +     * \brief Checks if the mouse position is inside the bounds of the button. +     * \param eventData The event data containing the mouse position. +     * \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 SDLContext::EventData & eventData, 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 560e2bc..85ec466 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -19,5 +19,4 @@ endfunction()  add_example(asset_manager)  add_example(savemgr)  add_example(rendering_particle) -add_example(gameloop) 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 d310f6a..dd4a754 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -12,4 +12,5 @@ target_sources(test_main PUBLIC  	ValueBrokerTest.cpp  	DBTest.cpp  	Vector2Test.cpp +	InputTest.cpp  ) diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp index b0e6c9c..c75bcc1 100644 --- a/src/test/EventTest.cpp +++ b/src/test/EventTest.cpp @@ -158,29 +158,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..4f6077b --- /dev/null +++ b/src/test/InputTest.cpp @@ -0,0 +1,250 @@ +#include <gtest/gtest.h> +#define protected public +#define private public +#include "api/EventManager.h" +#include "api/KeyCodes.h" +#include "system/InputSystem.h" +#include <SDL2/SDL.h> +#include <SDL2/SDL_keycode.h> +#include <crepe/ComponentManager.h> +#include <crepe/api/Button.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: +	ComponentManager mgr{}; +	InputSystem input_system{mgr}; // Initializes the InputSystem with the ComponentManager + +	EventManager & event_manager = EventManager::get_instance(); + +protected: +	void SetUp() override { 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) { +	bool mouse_triggered = false; +	EventHandler<MousePressEvent> on_mouse_down = [&](const MousePressEvent & event) { +		mouse_triggered = true; +		EXPECT_EQ(event.mouse_x, 10); +		EXPECT_EQ(event.mouse_y, 10); +		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; +	event.button.x = 10; +	event.button.y = 10; +	event.button.button = SDL_BUTTON_LEFT; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(mouse_triggered); +} + +TEST_F(InputTest, MouseUp) { +	bool function_triggered = false; +	EventHandler<MouseReleaseEvent> on_mouse_release = [&](const MouseReleaseEvent & e) { +		function_triggered = true; +		EXPECT_EQ(e.mouse_x, 10); +		EXPECT_EQ(e.mouse_y, 10); +		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 = 10; +	event.button.y = 10; +	event.button.button = SDL_BUTTON_LEFT; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseMove) { +	bool function_triggered = false; +	EventHandler<MouseMoveEvent> on_mouse_move = [&](const MouseMoveEvent & e) { +		function_triggered = true; +		EXPECT_EQ(e.mouse_x, 10); +		EXPECT_EQ(e.mouse_y, 10); +		EXPECT_EQ(e.rel_x, 10); +		EXPECT_EQ(e.rel_y, 10); +		return false; +	}; +	event_manager.subscribe<MouseMoveEvent>(on_mouse_move); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEMOTION; +	event.motion.x = 10; +	event.motion.y = 10; +	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) { +	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) { +	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) { +	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, 10); +		EXPECT_EQ(event.mouse_y, 10); +		return false; +	}; +	event_manager.subscribe<MouseClickEvent>(on_mouse_click); + +	this->simulate_mouse_click(10, 10, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(on_click_triggered); +} + +TEST_F(InputTest, testButtonClick) { +	GameObject 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 = obj.add_component<Button>(100, 100, on_click, false); + +	bool hover = false; +	button.active = true; + +	button.is_pressed = false; +	button.is_toggle = false; +	this->simulate_mouse_click(101, 101, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_FALSE(button_clicked); + +	this->simulate_mouse_click(10, 10, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(button_clicked); +} + +TEST_F(InputTest, testButtonHover) { +	GameObject 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 = obj.add_component<Button>(100, 100, on_click, false); +	button.active = true; +	button.width = 100; +	button.height = 100; +	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 = 200; +	event.motion.y = 200; +	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 = 10; +	hover_event.motion.y = 10; +	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); +} |