aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/crepe/api/Button.cpp13
-rw-r--r--src/crepe/api/Button.h69
-rw-r--r--src/crepe/api/CMakeLists.txt4
-rw-r--r--src/crepe/api/Event.h20
-rw-r--r--src/crepe/api/KeyCodes.h21
-rw-r--r--src/crepe/api/LoopManager.cpp6
-rw-r--r--src/crepe/api/UiObject.cpp8
-rw-r--r--src/crepe/api/UiObject.h33
-rw-r--r--src/crepe/facade/SDLContext.cpp215
-rw-r--r--src/crepe/facade/SDLContext.h73
-rw-r--r--src/crepe/system/CMakeLists.txt2
-rw-r--r--src/crepe/system/InputSystem.cpp144
-rw-r--r--src/crepe/system/InputSystem.h86
-rw-r--r--src/example/CMakeLists.txt1
-rw-r--r--src/example/gameloop.cpp7
-rw-r--r--src/test/CMakeLists.txt1
-rw-r--r--src/test/EventTest.cpp36
-rw-r--r--src/test/InputTest.cpp248
18 files changed, 923 insertions, 64 deletions
diff --git a/src/crepe/api/Button.cpp b/src/crepe/api/Button.cpp
new file mode 100644
index 0000000..c0ff5a8
--- /dev/null
+++ b/src/crepe/api/Button.cpp
@@ -0,0 +1,13 @@
+#include "Button.h"
+
+namespace crepe {
+
+Button::Button(game_object_id_t id, int width, int height, bool is_toggle,
+ std::function<void()> on_click)
+ : UiObject(id, width, height),
+ is_toggle(is_toggle),
+ is_pressed(false),
+ hover(false),
+ 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..2fa94ae
--- /dev/null
+++ b/src/crepe/api/Button.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "UiObject.h"
+#include <functional>
+
+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, bool is_toggle = false,
+ std::function<void()> on_click = nullptr);
+
+ /**
+ * \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;
+
+ /**
+ * \brief Indicates whether the button is currently pressed.
+ *
+ * This state is true when the button is actively pressed and false otherwise.
+ */
+ bool is_pressed;
+
+ /**
+ * \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;
+
+ /**
+ * \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()`. Defaults to nullptr.
+ */
+ std::function<void()> on_click;
+
+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..aeb451d 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -23,6 +23,8 @@ target_sources(crepe PUBLIC
Asset.cpp
EventHandler.cpp
Script.cpp
+ Button.cpp
+ UiObject.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -58,4 +60,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..a7d5511 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;
+
+ // Relative movement in x
+ int rel_x = 0;
+
+ // Relative movement 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/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/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 b3298a7..735ed70 100644
--- a/src/crepe/facade/SDLContext.cpp
+++ b/src/crepe/facade/SDLContext.cpp
@@ -5,6 +5,7 @@
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_surface.h>
#include <SDL2/SDL_video.h>
+#include <array>
#include <cmath>
#include <cstddef>
#include <functional>
@@ -13,6 +14,7 @@
#include <string>
#include "../api/Camera.h"
+#include "../api/EventManager.h"
#include "../api/Sprite.h"
#include "../api/Texture.h"
#include "../api/Transform.h"
@@ -72,28 +74,145 @@ 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() { 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 {
@@ -191,4 +310,64 @@ int SDLContext::get_height(const Texture & ctx) const {
SDL_QueryTexture(ctx.texture.get(), NULL, NULL, NULL, &h);
return h;
}
+
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},
+ .wheel_delta = event.wheel.y,
+ });
+ } break;
+ }
+ }
+ return event_list;
+}
diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h
index 20e30b3..886dda8 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,19 +9,20 @@
#include <functional>
#include <memory>
#include <string>
+#include <utility>
-#include "../api/Sprite.h"
-#include "../api/Transform.h"
#include "api/Camera.h"
+#include "api/Event.h"
+#include "api/KeyCodes.h"
+#include "api/Sprite.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
@@ -31,6 +33,28 @@ typedef SDL_Keycode CREPE_KEYCODES;
class SDLContext {
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.
@@ -43,13 +67,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 sdlKey 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..070f804
--- /dev/null
+++ b/src/crepe/system/InputSystem.cpp
@@ -0,0 +1,144 @@
+#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>();
+ RefVector<Transform> transforms = mgr.get_components_by_type<Transform>();
+
+ for (Button & button : buttons) {
+ OptionalRef<Transform> transform = find_transform_for_button(button, transforms);
+ if (!transform) continue;
+
+ if (button.active && is_mouse_inside_button(event_data, button, transform)) {
+ button.hover = true;
+ } else {
+ button.hover = false;
+ }
+ }
+}
+
+void InputSystem::handle_click(const SDLContext::EventData & event_data) {
+ ComponentManager & mgr = this->component_manager;
+
+ RefVector<Button> buttons = mgr.get_components_by_type<Button>();
+ RefVector<Transform> transforms = mgr.get_components_by_type<Transform>();
+
+ for (Button & button : buttons) {
+ OptionalRef<Transform> transform_ref = find_transform_for_button(button, transforms);
+
+ if (button.active && is_mouse_inside_button(event_data, button, transform_ref)) {
+ handle_button_press(button);
+ }
+ }
+}
+
+OptionalRef<Transform>
+InputSystem::find_transform_for_button(Button & button, RefVector<Transform> & transforms) {
+
+ for (auto & transform : transforms) {
+ if (button.game_object_id == transform.get().game_object_id) {
+ return OptionalRef<Transform>(transform);
+ }
+ }
+
+ return OptionalRef<Transform>();
+}
+
+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..b648144
--- /dev/null
+++ b/src/crepe/system/InputSystem.h
@@ -0,0 +1,86 @@
+#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 Finds the transform component associated with a button.
+ * \param button The button to find the associated transform for.
+ * \param transforms A list of transforms to search through.
+ * \return A pointer to the transform of the button, or nullptr if not found.
+ */
+ OptionalRef<Transform> find_transform_for_button(Button & button,
+ RefVector<Transform> & transforms);
+
+ /**
+ * \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..d6ebb6f
--- /dev/null
+++ b/src/test/InputTest.cpp
@@ -0,0 +1,248 @@
+#include <gtest/gtest.h>
+#define protected 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);
+
+ auto & button = obj.add_component<Button>(100, 100);
+ bool button_clicked = false;
+ bool hover = false;
+ button.active = true;
+ std::function<void()> on_click = [&]() { button_clicked = true; };
+ button.on_click = on_click;
+ 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);
+ auto & button = obj.add_component<Button>(100, 100);
+ bool button_clicked = 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);
+}