diff options
Diffstat (limited to 'src')
-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 |
22 files changed, 1029 insertions, 75 deletions
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); +} |