From dbe642e5e26cf2bd99218f727157b58c7fed286e Mon Sep 17 00:00:00 2001 From: hachem Date: Tue, 12 Aug 2025 16:35:42 +0200 Subject: feat: application manager and EventDispatcher --- src/core/application.cpp | 86 ++++++++++++ src/core/application.h | 35 +++++ src/core/event.h | 338 +++++++++++++++++++++++++++++++++++++++++++++++ src/core/window.cpp | 157 +++++++++++++++++++++- src/core/window.h | 32 ++++- 5 files changed, 641 insertions(+), 7 deletions(-) create mode 100644 src/core/application.cpp create mode 100644 src/core/application.h create mode 100644 src/core/event.h (limited to 'src/core') diff --git a/src/core/application.cpp b/src/core/application.cpp new file mode 100644 index 0000000..d98ae4f --- /dev/null +++ b/src/core/application.cpp @@ -0,0 +1,86 @@ +#include "application.h" +#include + +namespace Donut +{ + Application* Application::s_instance = nullptr; + + Application::Application(const std::string& name, int width, int height) + : m_running(true), m_minimized(false) + { + s_instance = this; + + m_window = std::make_unique(name, width, height); + m_window->set_event_callback([this](Event& event) + { + on_event(event); + }); + + on_init(); + } + + Application::~Application() + { + on_shutdown(); + s_instance = nullptr; + } + + void Application::run() + { + while (m_running) + { + if (!m_minimized) + { + on_update(); + on_render(); + } + + m_window->on_update(); + } + } + + void Application::close() + { + m_running = false; + } + + void Application::on_event(Event& event) + { + EventDispatcher dispatcher(event); + dispatcher.dispatch([this, &event](WindowCloseEvent& e) + { + m_running = false; + event.handled = true; + return true; + }); + + dispatcher.dispatch([this, &event](WindowResizeEvent& e) + { + if (e.get_width() == 0 || e.get_height() == 0) + m_minimized = true; + else + m_minimized = false; + + event.handled = true; + return true; + }); + + dispatcher.dispatch([this, &event](KeyPressedEvent& e) + { + if (e.get_key_code() == 256) + close(); + return true; + }); + } + + void Application::on_init() { } + void Application::on_shutdown() { } + + void Application::on_update() + { + } + + void Application::on_render() + { + } +} diff --git a/src/core/application.h b/src/core/application.h new file mode 100644 index 0000000..d88eab2 --- /dev/null +++ b/src/core/application.h @@ -0,0 +1,35 @@ +#pragma once + +#include "window.h" +#include "event.h" +#include +#include + +namespace Donut +{ + class Application + { + public: + Application(const std::string& name = "Donut", + int width = 1280, int height = 720); + ~Application(); + + void run(); + void close(); + + Window& get_window() { return *m_window; } + static Application& get() { return *s_instance; } + private: + void on_init(); + void on_shutdown(); + void on_update(); + void on_render(); + void on_event(Event& event); + private: + std::unique_ptr m_window; + bool m_running; + bool m_minimized; + + static Application* s_instance; + }; +} diff --git a/src/core/event.h b/src/core/event.h new file mode 100644 index 0000000..6639f28 --- /dev/null +++ b/src/core/event.h @@ -0,0 +1,338 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Donut +{ + class Event; + class EventDispatcher; + + enum class EventType + { + None = 0, + + WindowClose, + WindowResize, + WindowFocus, + WindowLostFocus, + WindowMoved, + + KeyPressed, + KeyReleased, + KeyTyped, + + MouseButtonPressed, + MouseButtonReleased, + MouseMoved, + MouseScrolled + }; + + enum EventCategory + { + None = 0, + EventCategoryApplication = 1 << 0, + EventCategoryInput = 1 << 1, + EventCategoryKeyboard = 1 << 2, + EventCategoryMouse = 1 << 3, + EventCategoryMouseButton = 1 << 4 + }; + + class Event + { + public: + virtual ~Event() = default; + + bool handled = false; + + virtual EventType get_event_type() const = 0; + virtual const char* get_name() const = 0; + virtual int get_category_flags() const = 0; + virtual std::string to_string() const { return get_name(); } + + bool is_in_category(EventCategory category) { return get_category_flags() & category; } + }; + + #define EVENT_CLASS_TYPE(type) \ + static EventType get_static_type() { return EventType::type; } \ + virtual EventType get_event_type() const override { return get_static_type(); } \ + virtual const char* get_name() const override { return #type; } + + #define APPLICATION_EVENT_CLASS_TYPE(type) \ + EVENT_CLASS_TYPE(type) \ + virtual int get_category_flags() const override { return EventCategoryApplication; } + + #define MOUSE_EVENT_CLASS_TYPE(type) \ + EVENT_CLASS_TYPE(type) \ + virtual int get_category_flags() const override { return EventCategoryMouse | \ + EventCategoryInput; } + + #define MOUSE_BUTTON_EVENT_CLASS_TYPE(type) \ + EVENT_CLASS_TYPE(type) \ + virtual int get_category_flags() const override { return EventCategoryMouseButton | \ + EventCategoryMouse | \ + EventCategoryInput; } + + #define KEYBOARD_EVENT_CLASS_TYPE(type) \ + EVENT_CLASS_TYPE(type) \ + virtual int get_category_flags() const override { return EventCategoryKeyboard | \ + EventCategoryInput; } + + class WindowResizeEvent : public Event + { + public: + WindowResizeEvent(unsigned int width, unsigned int height) + : m_width(width), m_height(height) { } + + unsigned int get_width() const { return m_width; } + unsigned int get_height() const { return m_height; } + + std::string to_string() const override + { + return "WindowResizeEvent: " + std::to_string(m_width) + ", " + std::to_string(m_height); + } + + APPLICATION_EVENT_CLASS_TYPE(WindowResize) + + private: + unsigned int m_width, m_height; + }; + + class WindowCloseEvent : public Event + { + public: + WindowCloseEvent() = default; + APPLICATION_EVENT_CLASS_TYPE(WindowClose) + }; + + class WindowFocusEvent : public Event + { + public: + WindowFocusEvent() = default; + APPLICATION_EVENT_CLASS_TYPE(WindowFocus) + }; + + class WindowLostFocusEvent : public Event + { + public: + WindowLostFocusEvent() = default; + APPLICATION_EVENT_CLASS_TYPE(WindowLostFocus) + }; + + class WindowMovedEvent : public Event + { + public: + WindowMovedEvent(int x, int y) + : m_x(x), m_y(y) { } + + int get_x() const { return m_x; } + int get_y() const { return m_y; } + + std::string to_string() const override + { + return "WindowMovedEvent: " + std::to_string(m_x) + ", " + std::to_string(m_y); + } + + APPLICATION_EVENT_CLASS_TYPE(WindowMoved) + + private: + int m_x, m_y; + }; + + class KeyEvent : public Event + { + public: + int get_key_code() const { return m_key_code; } + virtual int get_category_flags() const override { return EventCategoryKeyboard | + EventCategoryInput; } + + protected: + KeyEvent(int keycode) : m_key_code(keycode) { } + int m_key_code; + }; + + class KeyPressedEvent : public KeyEvent + { + public: + KeyPressedEvent(int keycode, bool is_repeat = false) + : KeyEvent(keycode), m_is_repeat(is_repeat) { } + + bool is_repeat() const { return m_is_repeat; } + + std::string to_string() const override + { + return "KeyPressedEvent: " + std::to_string(m_key_code) + + " (repeat = " + std::to_string(m_is_repeat) + ")"; + } + + KEYBOARD_EVENT_CLASS_TYPE(KeyPressed) + + private: + bool m_is_repeat; + }; + + class KeyReleasedEvent : public KeyEvent + { + public: + KeyReleasedEvent(int keycode) : KeyEvent(keycode) { } + + std::string to_string() const override + { + return "KeyReleasedEvent: " + std::to_string(m_key_code); + } + + KEYBOARD_EVENT_CLASS_TYPE(KeyReleased) + }; + + class KeyTypedEvent : public KeyEvent + { + public: + KeyTypedEvent(int keycode) : KeyEvent(keycode) { } + + std::string to_string() const override + { + return "KeyTypedEvent: " + std::to_string(m_key_code); + } + + KEYBOARD_EVENT_CLASS_TYPE(KeyTyped) + }; + + class MouseMovedEvent : public Event + { + public: + MouseMovedEvent(float x, float y) + : m_mouse_x(x), m_mouse_y(y) { } + + float get_x() const { return m_mouse_x; } + float get_y() const { return m_mouse_y; } + + std::string to_string() const override + { + return "MouseMovedEvent: " + std::to_string(m_mouse_x) + ", " + std::to_string(m_mouse_y); + } + + MOUSE_EVENT_CLASS_TYPE(MouseMoved) + + private: + float m_mouse_x, m_mouse_y; + }; + + class MouseScrolledEvent : public Event + { + public: + MouseScrolledEvent(float x_offset, float y_offset) + : m_x_offset(x_offset), m_y_offset(y_offset) { } + + float get_x_offset() const { return m_x_offset; } + float get_y_offset() const { return m_y_offset; } + + std::string to_string() const override + { + return "MouseScrolledEvent: " + std::to_string(m_x_offset) + + ", " + std::to_string(m_y_offset); + } + + MOUSE_EVENT_CLASS_TYPE(MouseScrolled) + + private: + float m_x_offset, m_y_offset; + }; + + class MouseButtonEvent : public Event + { + public: + int get_mouse_button() const { return m_button; } + virtual int get_category_flags() const override { return EventCategoryMouseButton | + EventCategoryMouse | + EventCategoryInput; } + + protected: + MouseButtonEvent(int button) : m_button(button) { } + int m_button; + }; + + class MouseButtonPressedEvent : public MouseButtonEvent + { + public: + MouseButtonPressedEvent(int button) : MouseButtonEvent(button) { } + + std::string to_string() const override + { + return "MouseButtonPressedEvent: " + std::to_string(m_button); + } + + MOUSE_BUTTON_EVENT_CLASS_TYPE(MouseButtonPressed) + }; + + class MouseButtonReleasedEvent : public MouseButtonEvent + { + public: + MouseButtonReleasedEvent(int button) : MouseButtonEvent(button) { } + + std::string to_string() const override + { + return "MouseButtonReleasedEvent: " + std::to_string(m_button); + } + + MOUSE_BUTTON_EVENT_CLASS_TYPE(MouseButtonReleased) + }; + + class EventDispatcher + { + public: + EventDispatcher(Event& event) : m_event(event) { } + + template + bool dispatch(const F& func) + { + if (m_event.get_event_type() == T::get_static_type()) + { + m_event.handled = func(static_cast(m_event)); + return true; + } + return false; + } + + private: + Event& m_event; + }; + + class EventHandler + { + public: + using EventCallbackFn = std::function; + + EventHandler() = default; + ~EventHandler() = default; + + void set_event_callback(const EventCallbackFn& callback) { m_event_callback = callback; } + + void on_event(Event& event) + { + if (m_event_callback) + m_event_callback(event); + } + + template + void bind_event(const std::function& callback) + { + m_event_callback = [callback](Event& event) + { + if (event.get_event_type() == T::get_static_type()) + callback(static_cast(event)); + }; + } + + private: + EventCallbackFn m_event_callback; + }; + + #undef EVENT_CLASS_TYPE + #undef APPLICATION_EVENT_CLASS_TYPE + #undef MOUSE_EVENT_CLASS_TYPE + #undef MOUSE_BUTTON_EVENT_CLASS_TYPE + #undef KEYBOARD_EVENT_CLASS_TYPE +} diff --git a/src/core/window.cpp b/src/core/window.cpp index 3a6ba2c..ab5b977 100644 --- a/src/core/window.cpp +++ b/src/core/window.cpp @@ -1,5 +1,6 @@ #include "window.h" #include +#include namespace Donut { @@ -7,21 +8,57 @@ namespace Donut static uint32_t s_glfw_window_count = 0; Window::Window(const std::string& title, int width, int height) - : m_title(title), m_width(width), m_height(height) + : m_title(title), m_width(width), + m_height(height), m_is_closed(false) + { + init(); + } + + Window::~Window() + { + shutdown(); + } + + void Window::init() { if (!s_glfw_initialized) { - glfwInit(); + int success = glfwInit(); + if (!success) + { + std::cout << "Could not initialize GLFW!" << std::endl; + return; + } + s_glfw_initialized = true; } - m_window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + m_window = glfwCreateWindow(m_width, m_height, m_title.c_str(), nullptr, nullptr); + if (!m_window) + { + std::cout << "Could not create GLFW window!" << std::endl; + glfwTerminate(); + return; + } + glfwMakeContextCurrent(m_window); + glfwSetWindowUserPointer(m_window, this); + + glfwSetErrorCallback(glfw_error_callback); + glfwSetWindowCloseCallback(m_window, glfw_window_close_callback); + glfwSetWindowSizeCallback(m_window, glfw_window_size_callback); + glfwSetWindowFocusCallback(m_window, glfw_window_focus_callback); + glfwSetWindowPosCallback(m_window, glfw_window_pos_callback); + glfwSetKeyCallback(m_window, glfw_key_callback); + glfwSetCharCallback(m_window, glfw_char_callback); + glfwSetMouseButtonCallback(m_window, glfw_mouse_button_callback); + glfwSetScrollCallback(m_window, glfw_mouse_scroll_callback); + glfwSetCursorPosCallback(m_window, glfw_cursor_pos_callback); s_glfw_window_count++; } - Window::~Window() + void Window::shutdown() { glfwDestroyWindow(m_window); s_glfw_window_count--; @@ -41,6 +78,116 @@ namespace Donut bool Window::should_close() const { - return glfwWindowShouldClose(m_window); + return glfwWindowShouldClose(m_window) || m_is_closed; + } + + void Window::glfw_error_callback(int error, const char* description) + { + std::cout << "GLFW Error (" << error << "): " << description << std::endl; + } + + void Window::glfw_window_close_callback(GLFWwindow* window) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + WindowCloseEvent event; + win->m_event_handler.on_event(event); + } + + void Window::glfw_window_size_callback(GLFWwindow* window, int width, int height) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + win->m_width = width; + win->m_height = height; + + WindowResizeEvent event(width, height); + win->m_event_handler.on_event(event); + } + + void Window::glfw_window_focus_callback(GLFWwindow* window, int focused) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + + if (focused) + { + WindowFocusEvent event; + win->m_event_handler.on_event(event); + } + else + { + WindowLostFocusEvent event; + win->m_event_handler.on_event(event); + } + } + + void Window::glfw_window_pos_callback(GLFWwindow* window, int xpos, int ypos) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + WindowMovedEvent event(xpos, ypos); + win->m_event_handler.on_event(event); + } + + void Window::glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + + switch (action) + { + case GLFW_PRESS: + { + KeyPressedEvent event(key, false); + win->m_event_handler.on_event(event); + } break; + case GLFW_RELEASE: + { + KeyReleasedEvent event(key); + win->m_event_handler.on_event(event); + } break; + case GLFW_REPEAT: + { + KeyPressedEvent event(key, true); + win->m_event_handler.on_event(event); + } break; + } + } + + void Window::glfw_char_callback(GLFWwindow* window, unsigned int keycode) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + KeyTypedEvent event(keycode); + win->m_event_handler.on_event(event); + } + + void Window::glfw_mouse_button_callback(GLFWwindow* window, int button, int action, int mods) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + + switch (action) + { + case GLFW_PRESS: + { + MouseButtonPressedEvent event(button); + win->m_event_handler.on_event(event); + } break; + case GLFW_RELEASE: + { + MouseButtonReleasedEvent event(button); + win->m_event_handler.on_event(event); + break; + } + } + } + + void Window::glfw_mouse_scroll_callback(GLFWwindow* window, double x_offset, double y_offset) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + MouseScrolledEvent event((float)x_offset, (float)y_offset); + win->m_event_handler.on_event(event); + } + + void Window::glfw_cursor_pos_callback(GLFWwindow* window, double x_pos, double y_pos) + { + Window* win = static_cast(glfwGetWindowUserPointer(window)); + MouseMovedEvent event((float)x_pos, (float)y_pos); + win->m_event_handler.on_event(event); } } diff --git a/src/core/window.h b/src/core/window.h index 5f0e36e..8e9778e 100644 --- a/src/core/window.h +++ b/src/core/window.h @@ -2,23 +2,51 @@ #include #include +#include "event.h" namespace Donut { class Window { public: + using EventCallbackFn = std::function; + Window(const std::string& title, int width, int height); ~Window(); bool should_close() const; void on_update() const; + + void set_event_callback(const EventCallbackFn& callback) { m_event_handler.set_event_callback(callback); } + EventHandler& get_event_handler() { return m_event_handler; } + + unsigned int get_width() const { return m_width; } + unsigned int get_height() const { return m_height; } + + void* get_native_window() const { return m_window; } + + private: + void init(); + void shutdown(); + + static void glfw_error_callback(int error, const char* description); + static void glfw_window_close_callback(GLFWwindow* window); + static void glfw_window_size_callback(GLFWwindow* window, int width, int height); + static void glfw_window_focus_callback(GLFWwindow* window, int focused); + static void glfw_window_pos_callback(GLFWwindow* window, int xpos, int ypos); + static void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); + static void glfw_char_callback(GLFWwindow* window, unsigned int keycode); + static void glfw_mouse_button_callback(GLFWwindow* window, int button, int action, int mods); + static void glfw_mouse_scroll_callback(GLFWwindow* window, double x_offset, double y_offset); + static void glfw_cursor_pos_callback(GLFWwindow* window, double x_pos, double y_pos); + private: GLFWwindow* m_window; + EventHandler m_event_handler; std::string m_title; - int m_width; - int m_height; + unsigned int m_width; + unsigned int m_height; bool m_is_closed; }; } -- cgit v1.3