diff options
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/application.cpp | 238 | ||||
| -rw-r--r-- | src/core/application.h | 53 | ||||
| -rw-r--r-- | src/core/camera.cpp | 225 | ||||
| -rw-r--r-- | src/core/camera.h | 131 | ||||
| -rw-r--r-- | src/core/event.h | 362 | ||||
| -rw-r--r-- | src/core/hdri_manager.cpp | 64 | ||||
| -rw-r--r-- | src/core/hdri_manager.h | 48 | ||||
| -rw-r--r-- | src/core/log.cpp | 156 | ||||
| -rw-r--r-- | src/core/log.h | 156 | ||||
| -rw-r--r-- | src/core/memory.h | 24 | ||||
| -rw-r--r-- | src/core/settings_manager.cpp | 200 | ||||
| -rw-r--r-- | src/core/settings_manager.h | 82 | ||||
| -rw-r--r-- | src/core/state.h | 19 | ||||
| -rw-r--r-- | src/core/state_manager.cpp | 88 | ||||
| -rw-r--r-- | src/core/state_manager.h | 41 | ||||
| -rw-r--r-- | src/core/theme_manager.cpp | 291 | ||||
| -rw-r--r-- | src/core/theme_manager.h | 27 | ||||
| -rw-r--r-- | src/core/window.cpp | 341 | ||||
| -rw-r--r-- | src/core/window.h | 78 |
19 files changed, 2624 insertions, 0 deletions
diff --git a/src/core/application.cpp b/src/core/application.cpp new file mode 100644 index 0000000..667fc73 --- /dev/null +++ b/src/core/application.cpp @@ -0,0 +1,238 @@ +#include "application.h" + +#include "rendering/renderer.h" +#include "settings_manager.h" + +#include "states/simulation_state.h" +#include "states/config_state.h" +#include "states/world_builder_state.h" + +#include <GLFW/glfw3.h> +#include <imgui.h> +#include <ImGuizmo.h> + +#include "platform/vulkan/vulkan_renderer.h" + +#ifdef __APPLE__ +#include "platform/metal/metal_context.h" +#include "platform/vulkan/vulkan_context.h" +#endif + +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; + + // Select the render API before the window is created: the window is + // built differently for Vulkan (GLFW_NO_API) than for OpenGL. + Logger::init(); + SettingsManager::initialize(); + { + const auto& settings = SettingsManager::get_settings_const(); + RendererAPI::set_api(settings.graphics.render_api == "Vulkan" + ? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL); + } + + if (RendererAPI::get_api() == RendererAPI::API::Vulkan) + vulkan_prepare_glfw(); // must precede glfwInit() inside the Window ctor + + m_window = create_scope<Window>(name, width, height); + m_window->set_event_callback([this](Event& event) + { + on_event(event); + }); + + on_init(); + } + + Application::~Application() + { + on_shutdown(); + s_instance = nullptr; + } + + auto Application::run() -> void + { + while (m_running) + { + if (m_vulkan_renderer) + { + glfwPollEvents(); // before the ImGui frame so input is current + if (!m_minimized) + m_vulkan_renderer->draw_frame(glm::vec4(0.05f, 0.06f, 0.10f, 1.0f), + [this] { build_vulkan_ui(); }); + } + else + { + if (!m_minimized) + { + on_update(); + on_render(); + } + m_window->on_update(); // polls events + swaps buffers (OpenGL) + } + } + } + + auto Application::build_vulkan_ui() -> void + { + // Placeholder UI proving the ImGui Vulkan backend renders. The real + // application UI (states' on_im_ui_render) moves here once the scene is + // ported to Vulkan (B-3). + ImGui::Begin("Donut - Vulkan backend"); + ImGui::Text("Geodesic black hole through Vulkan (MoltenVK)."); + ImGui::Text("%.1f FPS", ImGui::GetIO().Framerate); + ImGui::Separator(); + ImGui::TextWrapped("Drag to orbit, scroll to zoom. HDRI starfield is live; " + "next is porting the simulation UI to Vulkan."); + ImGui::End(); + } + + auto Application::close() -> void + { + m_running = false; + } + + auto Application::on_event(Event& event) -> void + { + EventDispatcher dispatcher(event); + dispatcher.dispatch<WindowCloseEvent>([this, &event](WindowCloseEvent& e) + { + m_running = false; + event.handled = true; + return true; + }); + + dispatcher.dispatch<WindowResizeEvent>([this, &event](WindowResizeEvent& e) + { + if (e.get_width() == 0 || e.get_height() == 0) + m_minimized = true; + else + m_minimized = false; + + if (m_vulkan_renderer) + { + m_vulkan_renderer->on_resize(e.get_width(), e.get_height()); + } + else + { + Renderer::on_window_resize(e.get_width(), e.get_height()); + if (m_engine) m_engine->set_window_dimensions(e.get_width(), e.get_height()); + } + event.handled = true; + return true; + }); + + dispatcher.dispatch<KeyPressedEvent>([this, &event](KeyPressedEvent& e) + { + if (!m_state_manager) + return true; + if (e.get_key_code() == GLFW_KEY_1) + { + m_state_manager->switch_to_state("Config"); + event.handled = true; + return true; + } + else if (e.get_key_code() == GLFW_KEY_2) + { + m_state_manager->switch_to_state("Simulation"); + event.handled = true; + return true; + } + else if (e.get_key_code() == GLFW_KEY_3) + { + m_state_manager->switch_to_state("WorldBuilder"); + event.handled = true; + return true; + } + + return true; + }); + + if (m_state_manager) + m_state_manager->on_event(event); + } + + auto Application::on_init() -> void + { + // (Logger, settings and API selection happen in the constructor.) + + if (RendererAPI::get_api() == RendererAPI::API::Vulkan) + { + int w = 0, h = 0; + glfwGetFramebufferSize((GLFWwindow*)m_window->get_native_window(), &w, &h); + m_vulkan_renderer = create_scope<VulkanRenderer>(); + if (!m_vulkan_renderer->init(m_window->get_native_window(), w, h)) + DONUT_ERROR("Vulkan renderer initialization failed"); + else + m_vulkan_renderer->init_im_gui(); + // Scene rendering hooks in here in the B-3 phase; the Vulkan path now + // clears, presents, and draws the ImGui UI. + return; + } + + Renderer::init(); + m_window->init_im_gui(); + + Renderer::on_window_resize(1280, 720); + RenderCommand::set_face_culling(false); + + m_engine = create_scope<Engine>(); + m_engine->set_window_dimensions(1280, 720); + + m_state_manager = create_scope<StateManager>(); + m_state_manager->register_state("Config", create_scope<ConfigState>()); + m_state_manager->register_state("Simulation", create_scope<SimulationState>()); + m_state_manager->register_state("WorldBuilder", create_scope<WorldBuilderState>()); + m_state_manager->switch_to_state("Config"); + } + + auto Application::on_shutdown() -> void + { + if (m_vulkan_renderer) + { + m_vulkan_renderer->shutdown(); + m_vulkan_renderer.reset(); + } + else + { + if (m_state_manager) + m_state_manager->shutdown(); + Renderer::shutdown(); + } + SettingsManager::shutdown(); + Logger::shutdown(); + } + + auto Application::on_update() -> void + { + float current_frame = (float)glfwGetTime(); + m_delta_time = current_frame - m_last_frame; + m_last_frame = current_frame; + + m_state_manager->update(m_delta_time); + } + + auto Application::on_render() -> void + { + m_state_manager->render(); + m_window->begin_im_gui_frame(); + + ImGuizmo::BeginFrame(); + ImGuizmo::SetOrthographic(false); + + setup_docking_layout(); + m_state_manager->on_im_ui_render(); + m_window->end_im_gui_frame(); + } + + auto Application::setup_docking_layout() -> void + { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::DockSpaceOverViewport(0, viewport, ImGuiDockNodeFlags_PassthruCentralNode); + } +}; diff --git a/src/core/application.h b/src/core/application.h new file mode 100644 index 0000000..06c4e94 --- /dev/null +++ b/src/core/application.h @@ -0,0 +1,53 @@ +#pragma once + +#include "state_manager.h" +#include "memory.h" +#include "window.h" +#include "event.h" +#include "log.h" + +#include "engine/engine.h" + +namespace Donut +{ + class VulkanRenderer; // live-window Vulkan backend (platform/vulkan) + + class Application + { + public: + Application(const std::string& name = "Donut", + int width = 1280, int height = 720); + ~Application(); + + auto run() -> void; + auto close() -> void; + + auto get_window() -> Window& { return *m_window; } + auto get_state_manager() -> StateManager& { return *m_state_manager; } + auto get_engine() -> Engine& { return *m_engine; } + static auto get() -> Application& { return *s_instance; } + + private: + auto on_init() -> void; + auto on_shutdown() -> void; + auto on_update() -> void; + auto on_render() -> void; + auto on_event(Event& event) -> void; + auto setup_docking_layout() -> void; + auto build_vulkan_ui() -> void; // ImGui UI built each frame on the Vulkan path + + private: + Scope<StateManager> m_state_manager; + Scope<Window> m_window; + Scope<Engine> m_engine; + Scope<VulkanRenderer> m_vulkan_renderer; // non-null only when the Vulkan API is selected + + bool m_running; + bool m_minimized; + + float m_delta_time = 0.0f; + float m_last_frame = 0.0f; + + static Application* s_instance; + }; +} diff --git a/src/core/camera.cpp b/src/core/camera.cpp new file mode 100644 index 0000000..35b7a2f --- /dev/null +++ b/src/core/camera.cpp @@ -0,0 +1,225 @@ +#include "camera.h" + +#include <glm/gtc/quaternion.hpp> +#include <GLFW/glfw3.h> + +namespace Donut +{ + Camera::Camera(float fov, float aspect_ratio, float near_plane, float far_plane) + : m_fov(fov), m_aspect_ratio(aspect_ratio), m_near_plane(near_plane), m_far_plane(far_plane) + { + recalculate_projection_matrix(); + recalculate_view_matrix(); + } + + auto Camera::set_projection(float fov, float aspect_ratio, float near_plane, float far_plane) -> void + { + m_fov = fov; + m_aspect_ratio = aspect_ratio; + m_near_plane = near_plane; + m_far_plane = far_plane; + recalculate_projection_matrix(); + } + + auto Camera::recalculate_projection_matrix() -> void + { + m_projection_matrix = glm::perspective(glm::radians(m_fov), m_aspect_ratio, m_near_plane, m_far_plane); + m_view_projection_matrix = m_projection_matrix * m_view_matrix; + } + + auto Camera::recalculate_view_matrix() -> void + { + if (m_camera_mode == CameraMode::FPS) + { + float pitch = glm::radians(m_rotation.x); + float yaw = glm::radians(m_rotation.y); + float roll = glm::radians(m_rotation.z); + + glm::vec3 direction; + direction.x = cos(yaw) * cos(pitch); + direction.y = sin(pitch); + direction.z = sin(yaw) * cos(pitch); + + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + glm::vec3 front = glm::normalize(direction); + glm::vec3 right = glm::normalize(glm::cross(front, world_up)); + glm::vec3 up = glm::normalize(glm::cross(right, front)); + + m_view_matrix = glm::lookAt(m_position, m_position + front, up); + m_view_projection_matrix = m_projection_matrix * m_view_matrix; + } + else if (m_camera_mode == CameraMode::Orbital) + { + glm::vec3 position = get_orbital_position(); + glm::vec3 target = m_orbital_target; + glm::vec3 up(0.0f, 1.0f, 0.0f); + + m_view_matrix = glm::lookAt(position, target, up); + m_view_projection_matrix = m_projection_matrix * m_view_matrix; + } + } + + auto Camera::get_forward_direction() const -> glm::vec3 + { + float pitch = glm::radians(m_rotation.x); + float yaw = glm::radians(m_rotation.y); + + glm::vec3 direction; + direction.x = cos(yaw) * cos(pitch); + direction.y = sin(pitch); + direction.z = sin(yaw) * cos(pitch); + + return glm::normalize(direction); + } + + auto Camera::get_right_direction() const -> glm::vec3 + { + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + return glm::normalize(glm::cross(get_forward_direction(), world_up)); + } + + auto Camera::get_up_direction() const -> glm::vec3 + { + return glm::normalize(glm::cross(get_right_direction(), get_forward_direction())); + } + + auto Camera::on_mouse_move(float x_offset, float y_offset, bool constrain_pitch) -> void + { + if (m_camera_mode == CameraMode::FPS) + { + x_offset *= m_mouse_sensitivity; + y_offset *= m_mouse_sensitivity; + + m_rotation.y += x_offset; + m_rotation.x += y_offset; + + if (constrain_pitch) + { + if (m_rotation.x > 89.0f) + m_rotation.x = 89.0f; + if (m_rotation.x < -89.0f) + m_rotation.x = -89.0f; + } + + recalculate_view_matrix(); + } + } + + auto Camera::move_forward(float delta_time) -> void + { + if (m_camera_mode == CameraMode::FPS) + { + m_position += get_forward_direction() * m_movement_speed * delta_time; + recalculate_view_matrix(); + } + } + + auto Camera::move_backward(float delta_time) -> void + { + if (m_camera_mode == CameraMode::FPS) + { + m_position -= get_forward_direction() * m_movement_speed * delta_time; + recalculate_view_matrix(); + } + } + + auto Camera::move_right(float delta_time) -> void + { + if (m_camera_mode == CameraMode::FPS) + { + m_position += get_right_direction() * m_movement_speed * delta_time; + recalculate_view_matrix(); + } + } + + auto Camera::move_left(float delta_time) -> void + { + if (m_camera_mode == CameraMode::FPS) + { + m_position -= get_right_direction() * m_movement_speed * delta_time; + recalculate_view_matrix(); + } + } + + auto Camera::move_up(float delta_time) -> void + { + if (m_camera_mode == CameraMode::FPS) + { + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + m_position += world_up * m_movement_speed * delta_time; + recalculate_view_matrix(); + } + } + + auto Camera::move_down(float delta_time) -> void + { + if (m_camera_mode == CameraMode::FPS) + { + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + m_position -= world_up * m_movement_speed * delta_time; + recalculate_view_matrix(); + } + } + + auto Camera::get_orbital_position() const -> glm::vec3 + { + float clamped_elevation = glm::clamp(m_elevation, 0.01f, float(std::numbers::pi) - 0.01f); + return glm::vec3 + ( + m_orbital_radius * sin(clamped_elevation) * cos(m_azimuth), + m_orbital_radius * cos(clamped_elevation), + m_orbital_radius * sin(clamped_elevation) * sin(m_azimuth) + ); + } + + auto Camera::update_orbital() -> void + { + m_orbital_target = glm::vec3(0.0f, 0.0f, 0.0f); + if (m_dragging || m_panning) + m_moving = true; + else + m_moving = false; + recalculate_view_matrix(); + } + + auto Camera::process_orbital_mouse_move(double x, double y) -> void + { + if (m_dragging && !m_panning) + { + float dx = float(x - m_last_x_orbital); + float dy = float(y - m_last_y_orbital); + + m_azimuth += dx * m_orbital_speed; + m_elevation -= dy * m_orbital_speed; + m_elevation = glm::clamp(m_elevation, 0.01f, float(std::numbers::pi) - 0.01f); + } + + m_last_x_orbital = x; + m_last_y_orbital = y; + update_orbital(); + } + + auto Camera::process_orbital_mouse_button(int button, int action, int mods) -> void + { + if (button == GLFW_MOUSE_BUTTON_LEFT) + { + if (action == GLFW_PRESS) + { + m_dragging = true; + m_panning = false; + } + else if (action == GLFW_RELEASE) + { + m_dragging = false; + m_panning = false; + } + } + } + + auto Camera::process_orbital_scroll(double x_offset, double y_offset) -> void + { + m_orbital_radius -= y_offset * m_zoom_speed; + m_orbital_radius = glm::clamp(m_orbital_radius, m_orbital_min_radius, m_orbital_max_radius); + update_orbital(); + } +} diff --git a/src/core/camera.h b/src/core/camera.h new file mode 100644 index 0000000..c997c35 --- /dev/null +++ b/src/core/camera.h @@ -0,0 +1,131 @@ +#pragma once + +#include <glm/glm.hpp> +#include <glm/gtc/matrix_transform.hpp> + +#include <numbers> + +namespace Donut +{ + enum class CameraMode + { + FPS, + Orbital + }; + + class Camera + { + public: + Camera(float fov = 45.0f, float aspect_ratio = 16.0f / 9.0f, + float near_plane = 0.1f, float far_plane = 100.0f); + ~Camera() = default; + + auto set_position(const glm::vec3& position) -> void { m_position = position; recalculate_view_matrix(); } + auto set_rotation(const glm::vec3& rotation) -> void { m_rotation = rotation; recalculate_view_matrix(); } + + auto get_position() const -> const glm::vec3& { return m_position; } + auto get_rotation() const -> const glm::vec3& { return m_rotation; } + + auto get_forward_direction() const -> glm::vec3; + auto get_right_direction() const -> glm::vec3; + auto get_up_direction() const -> glm::vec3; + + auto get_projection_matrix() const -> const glm::mat4& { return m_projection_matrix; } + auto get_view_matrix() const -> const glm::mat4& { return m_view_matrix; } + auto get_view_projection_matrix() const -> const glm::mat4& { return m_view_projection_matrix; } + + auto set_projection(float fov, float aspect_ratio, float near_plane, float far_plane) -> void; + auto on_mouse_move(float x_offset, float y_offset, bool constrain_pitch = true) -> void; + + auto move_forward(float delta_time) -> void; + auto move_backward(float delta_time) -> void; + auto move_right(float delta_time) -> void; + auto move_left(float delta_time) -> void; + auto move_up(float delta_time) -> void; + auto move_down(float delta_time) -> void; + + auto set_mouse_sensitivity(float sensitivity) -> void { m_mouse_sensitivity = sensitivity; } + auto get_mouse_sensitivity() const -> float { return m_mouse_sensitivity; } + + auto set_movement_speed(float speed) -> void { m_movement_speed = speed; } + auto get_movement_speed() const -> float { return m_movement_speed; } + + auto set_orbital_target(const glm::vec3& target) -> void { m_orbital_target = target; } + auto get_orbital_target() const -> const glm::vec3& { return m_orbital_target; } + + auto set_orbital_radius(double radius) -> void { m_orbital_radius = radius; } + auto get_orbital_radius() const -> double { return m_orbital_radius; } + + auto set_orbital_limits(double min_radius, double max_radius) -> void + { + m_orbital_min_radius = min_radius; + m_orbital_max_radius = max_radius; + } + + auto set_orbital_speed(float speed) -> void { m_orbital_speed = speed; } + auto get_orbital_speed() const -> float { return m_orbital_speed; } + + auto set_zoom_speed(double speed) -> void { m_zoom_speed = speed; } + auto get_zoom_speed() const -> double { return m_zoom_speed; } + + auto set_azimuth(float azimuth) -> void { m_azimuth = azimuth; } + auto get_azimuth() const -> float { return m_azimuth; } + + auto set_elevation(float elevation) -> void { m_elevation = elevation; } + auto get_elevation() const -> float { return m_elevation; } + + auto get_orbital_position() const -> glm::vec3; + auto update_orbital() -> void; + auto process_orbital_mouse_move(double x, double y) -> void; + auto process_orbital_mouse_button(int button, int action, int mods) -> void; + auto process_orbital_scroll(double x_offset, double y_offset) -> void; + + auto set_camera_mode(CameraMode mode) -> void { m_camera_mode = mode; } + auto get_camera_mode() const -> CameraMode { return m_camera_mode; } + + auto is_dragging() const -> bool { return m_dragging; } + auto is_panning() const -> bool { return m_panning; } + auto is_moving() const -> bool { return m_moving; } + auto get_last_x() const -> double { return m_last_x; } + auto get_last_y() const -> double { return m_last_y; } + + private: + auto recalculate_view_matrix() -> void; + auto recalculate_projection_matrix() -> void; + + private: + CameraMode m_camera_mode = CameraMode::FPS; + + glm::mat4 m_projection_matrix; + glm::mat4 m_view_matrix; + glm::mat4 m_view_projection_matrix; + + glm::vec3 m_position = { 0.0f, 0.0f, 3.0f }; + glm::vec3 m_rotation = { 0.0f, 0.0f, 0.0f }; + + float m_fov = 45.0f; + float m_aspect_ratio = 16.0f / 9.0f; + float m_near_plane = 0.1f; + float m_far_plane = 100.0f; + + float m_mouse_sensitivity = 0.1f; + float m_movement_speed = 5.0f; + bool m_first_mouse = true; + float m_last_x = 0.0f; + float m_last_y = 0.0f; + + glm::vec3 m_orbital_target = glm::vec3(0.0f, 0.0f, 0.0f); + double m_orbital_radius = 6.34194e10; + double m_orbital_min_radius = 1e10; + double m_orbital_max_radius = 1e12; + float m_azimuth = 0.0f; + float m_elevation = static_cast<float>(std::numbers::pi) / 2.0f; + float m_orbital_speed = 0.01f; + double m_zoom_speed = 25e9f; + bool m_dragging = false; + bool m_panning = false; + bool m_moving = false; + double m_last_x_orbital = 0.0; + double m_last_y_orbital = 0.0; + }; +} diff --git a/src/core/event.h b/src/core/event.h new file mode 100644 index 0000000..1891206 --- /dev/null +++ b/src/core/event.h @@ -0,0 +1,362 @@ +#pragma once + +#include <functional> +#include <unordered_map> +#include <vector> +#include <string> +#include <sstream> + +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; + + virtual auto get_event_type() const -> EventType = 0; + virtual auto get_name() const -> const char* = 0; + virtual auto get_category_flags() const -> int = 0; + virtual auto to_string() const -> std::string { return get_name(); } + + auto is_in_category(EventCategory category) -> bool { return get_category_flags() & category; } + + bool handled = false; + }; + + #define EVENT_CLASS_TYPE(type) \ + static auto get_static_type() -> EventType { return EventType::type; } \ + virtual auto get_event_type() const -> EventType override { return get_static_type(); } \ + virtual auto get_name() const -> const char* override { return #type; } + + #define APPLICATION_EVENT_CLASS_TYPE(type) \ + EVENT_CLASS_TYPE(type) \ + virtual auto get_category_flags() const -> int override { return EventCategoryApplication; } + + #define MOUSE_EVENT_CLASS_TYPE(type) \ + EVENT_CLASS_TYPE(type) \ + virtual auto get_category_flags() const -> int override { return EventCategoryMouse | \ + EventCategoryInput; } + + #define MOUSE_BUTTON_EVENT_CLASS_TYPE(type) \ + EVENT_CLASS_TYPE(type) \ + virtual auto get_category_flags() const -> int override { return EventCategoryMouseButton | \ + EventCategoryMouse | \ + EventCategoryInput; } + + #define KEYBOARD_EVENT_CLASS_TYPE(type) \ + EVENT_CLASS_TYPE(type) \ + virtual auto get_category_flags() const -> int override { return EventCategoryKeyboard | \ + EventCategoryInput; } + + class WindowResizeEvent + : public Event + { + public: + WindowResizeEvent(unsigned int width, unsigned int height) + : m_width(width), m_height(height) { } + + auto get_width() const -> unsigned int { return m_width; } + auto get_height() const -> unsigned int { return m_height; } + + auto to_string() const -> std::string 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) { } + + auto get_x() const -> int { return m_x; } + auto get_y() const -> int { return m_y; } + + auto to_string() const -> std::string 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: + auto get_key_code() const -> int { return m_key_code; } + virtual auto get_category_flags() const -> int 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) { } + + auto is_repeat() const -> bool { return m_is_repeat; } + + auto to_string() const -> std::string 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) { } + + auto to_string() const -> std::string override + { + return "KeyReleasedEvent: " + std::to_string(m_key_code); + } + + KEYBOARD_EVENT_CLASS_TYPE(KeyReleased) + }; + + class KeyTypedEvent + : public KeyEvent + { + public: + KeyTypedEvent(int keycode) + : KeyEvent(keycode) { } + + auto to_string() const -> std::string 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) { } + + auto get_x() const -> float { return m_mouse_x; } + auto get_y() const -> float { return m_mouse_y; } + + auto to_string() const -> std::string 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) { } + + auto get_x_offset() const -> float { return m_x_offset; } + auto get_y_offset() const -> float { return m_y_offset; } + + auto to_string() const -> std::string 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: + auto get_mouse_button() const -> int { return m_button; } + virtual auto get_category_flags() const -> int 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) { } + + auto to_string() const -> std::string override + { + return "MouseButtonPressedEvent: " + std::to_string(m_button); + } + + MOUSE_BUTTON_EVENT_CLASS_TYPE(MouseButtonPressed) + }; + + class MouseButtonReleasedEvent + : public MouseButtonEvent + { + public: + MouseButtonReleasedEvent(int button) + : MouseButtonEvent(button) { } + + auto to_string() const -> std::string override + { + return "MouseButtonReleasedEvent: " + std::to_string(m_button); + } + + MOUSE_BUTTON_EVENT_CLASS_TYPE(MouseButtonReleased) + }; + + class EventDispatcher + { + public: + EventDispatcher(Event& event) + : m_event(event) { } + + template<typename T, typename F> + auto dispatch(const F& func) -> bool + { + if (m_event.get_event_type() == T::get_static_type()) + { + m_event.handled = func(static_cast<T&>(m_event)); + return true; + } + return false; + } + + private: + Event& m_event; + }; + + class EventHandler + { + public: + using EventCallbackFn = std::function<void(Event&)>; + + EventHandler() = default; + ~EventHandler() = default; + + auto set_event_callback(const EventCallbackFn& callback) -> void + { + m_event_callback = callback; + } + + auto on_event(Event& event) -> void + { + if (m_event_callback) + m_event_callback(event); + } + + template<typename T> + auto bind_event(const std::function<void(T&)>& callback) -> void + { + m_event_callback = [callback](Event& event) + { + if (event.get_event_type() == T::get_static_type()) + callback(static_cast<T&>(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/hdri_manager.cpp b/src/core/hdri_manager.cpp new file mode 100644 index 0000000..605dcf6 --- /dev/null +++ b/src/core/hdri_manager.cpp @@ -0,0 +1,64 @@ +#include "hdri_manager.h" +#include <filesystem> + +namespace Donut +{ + auto HDRIManager::load_hdri(const std::string& path) -> Ref<CubemapTexture> + { + auto it = m_hdri_cache.find(path); + if (it != m_hdri_cache.end()) + { + DONUT_INFO("HDRI already cached: {}", path); + return it->second; + } + + DONUT_INFO("Loading HDRI: {}", path); + auto hdri = CubemapTexture::create_from_hdri(path); + + if (hdri) + { + m_hdri_cache[path] = hdri; + DONUT_INFO("Successfully loaded and cached HDRI: {}", path); + } + else + DONUT_ERROR("Failed to load HDRI: {}", path); + + return hdri; + } + + auto HDRIManager::set_current_hdri(const std::string& path) -> void + { + auto hdri = load_hdri(path); + if (hdri) + { + m_current_hdri = hdri; + DONUT_INFO("Set current HDRI to: {}", path); + } + else + DONUT_ERROR("Failed to set current HDRI: {}", path); + } + + auto HDRIManager::get_hdri_name(const std::string& path) const -> std::string + { + std::filesystem::path file_path(path); + std::string filename = file_path.stem().string(); + std::string name = filename; + + for (size_t i = 0; i < name.length(); ++i) + { + if (name[i] == '_') + name[i] = ' '; + else if (i == 0 || name[i-1] == ' ') + name[i] = std::toupper(name[i]); + } + + return name; + } + + auto HDRIManager::clear_cache() -> void + { + m_hdri_cache.clear(); + m_current_hdri.reset(); + DONUT_INFO("HDRI cache cleared"); + } +}; diff --git a/src/core/hdri_manager.h b/src/core/hdri_manager.h new file mode 100644 index 0000000..fa05330 --- /dev/null +++ b/src/core/hdri_manager.h @@ -0,0 +1,48 @@ +#pragma once + +#include "core/log.h" +#include "rendering/texture.h" + +#include <string> +#include <unordered_map> +#include <memory> + +namespace Donut +{ + class HDRIManager + { + public: + static auto get() -> HDRIManager& + { + static HDRIManager instance; + return instance; + } + + auto load_hdri(const std::string& path) -> Ref<CubemapTexture>; + auto get_current_hdri() const -> Ref<CubemapTexture> { return m_current_hdri; } + + auto set_current_hdri(const std::string& path) -> void; + auto get_available_hdri() const -> const std::vector<std::string>& { return m_available_hdri; } + auto get_hdri_name(const std::string& path) const -> std::string; + auto clear_cache() -> void; + + private: + HDRIManager() = default; + ~HDRIManager() = default; + + HDRIManager(const HDRIManager&) = delete; + HDRIManager& operator=(const HDRIManager&) = delete; + + std::unordered_map<std::string, Ref<CubemapTexture>> m_hdri_cache; + + Ref<CubemapTexture> m_current_hdri; + + std::vector<std::string> m_available_hdri = + { + "assets/hdri/HDR_blue_nebulae-1.hdr", + "assets/hdri/HDR_subdued_blue_nebulae.hdr", + "assets/hdri/HDR_subdued_multi_nebulae.hdr", + "assets/hdri/night_sky.hdr" + }; + }; +}; diff --git a/src/core/log.cpp b/src/core/log.cpp new file mode 100644 index 0000000..5a0434f --- /dev/null +++ b/src/core/log.cpp @@ -0,0 +1,156 @@ +#include "log.h" + +#include <filesystem> + +#if defined(DONUT_WINDOWS) + #include <windows.h> +#endif + +namespace Donut +{ + Ref<Logger> Logger::s_logger; + + auto Logger::init() -> void + { + s_logger = create_ref<Logger>(); + s_logger->set_log_level(LogLevel::Info); + s_logger->enable_console_output(true); + s_logger->enable_file_output(true); + s_logger->set_log_file("logs/donut.log"); + + s_logger->log_message(LogLevel::Info, "Logging system initialized"); + } + + auto Logger::shutdown() -> void + { + if (s_logger) + s_logger->log_message(LogLevel::Info, "Shutting down logging system"); + s_logger.reset(); + } + + auto Logger::get_logger() -> Ref<Logger> + { + return s_logger; + } + + Logger::Logger() + : m_log_level(LogLevel::Info), + m_console_output(true), + m_file_output(false) { } + + Logger::~Logger() + { + if (m_log_file.is_open()) + m_log_file.close(); + } + + auto Logger::set_log_file(const std::string& filename) -> void + { + std::filesystem::path log_path(filename); + std::filesystem::create_directories(log_path.parent_path()); + + m_log_file.open(filename, std::ios::app); + if (!m_log_file.is_open()) + std::cerr << "Failed to open log file: " << filename << std::endl; + } + + auto Logger::get_time_stamp() -> std::string + { + auto now = std::chrono::system_clock::now(); + auto time = std::chrono::system_clock::to_time_t(now); + + std::stringstream ss; + ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S"); + + return ss.str(); + } + + auto Logger::get_log_level_string(LogLevel level) -> std::string + { + switch (level) + { + case LogLevel::trace: return "TRACE"; + case LogLevel::Info: return "INFO"; + case LogLevel::Warn: return "WARN"; + case LogLevel::Err: return "ERROR"; + case LogLevel::Fatal: return "FATAL"; + default: return "UNKNOWN"; + } + } + + auto Logger::set_console_color(LogLevel level) -> void + { +#if defined(DONUT_WINDOWS) + HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE); + WORD color; + + switch (level) { + case LogLevel::trace: + color = FOREGROUND_RED | + FOREGROUND_GREEN | + FOREGROUND_BLUE; + break; + case LogLevel::Info: + color = FOREGROUND_GREEN | + FOREGROUND_INTENSITY; + break; + case LogLevel::Warn: + color = FOREGROUND_RED | + FOREGROUND_GREEN | + FOREGROUND_INTENSITY; + break; + case LogLevel::Err: + color = FOREGROUND_RED | + FOREGROUND_INTENSITY; + break; + case LogLevel::Fatal: + color = FOREGROUND_RED | + FOREGROUND_BLUE | + FOREGROUND_INTENSITY; + break; + default: + color = FOREGROUND_RED | + FOREGROUND_GREEN | + FOREGROUND_BLUE; + break; + } + + SetConsoleTextAttribute(h_console, color); +#else + const char* color_code; + switch (level) { + case LogLevel::trace: + color_code = "\033[37m"; + break; + case LogLevel::Info: + color_code = "\033[32;1m"; + break; + case LogLevel::Warn: + color_code = "\033[33;1m"; + break; + case LogLevel::Err: + color_code = "\033[31;1m"; + break; + case LogLevel::Fatal: + color_code = "\033[35;1m"; + break; + default: + color_code = "\033[37m"; + break; + } + std::cout << color_code; +#endif + } + + auto Logger::reset_console_color() -> void + { +#if defined(DONUT_WINDOWS) + HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(h_console, FOREGROUND_RED | + FOREGROUND_GREEN | + FOREGROUND_BLUE); +#else + std::cout << "\033[0m"; +#endif + } +} diff --git a/src/core/log.h b/src/core/log.h new file mode 100644 index 0000000..db6b4b4 --- /dev/null +++ b/src/core/log.h @@ -0,0 +1,156 @@ +#pragma once + +#include "core/memory.h" + +#include <string> +#include <vector> +#include <sstream> +#include <iostream> +#include <fstream> +#include <chrono> +#include <iomanip> +#include <mutex> +#include <string_view> + +namespace Donut +{ + enum class LogLevel + { + trace = 0, + Info = 2, + Warn = 3, + Err = 4, + Fatal = 5 + }; + + class Logger + { + public: + Logger(); + ~Logger(); + + static auto init() -> void; + static auto shutdown() -> void; + + static auto get_logger() -> Ref<Logger>; + + template<typename... Args> + static auto trace(const std::string_view& format, const Args&... args) -> void + { + auto logger = get_logger(); + if (logger) logger->log_message(LogLevel::trace, format, args...); + } + + template<typename... Args> + static auto info(const std::string_view& format, const Args&... args) -> void + { + auto logger = get_logger(); + if (logger) logger->log_message(LogLevel::Info, format, args...); + } + + template<typename... Args> + static auto warn(const std::string_view& format, const Args&... args) -> void + { + auto logger = get_logger(); + if (logger) logger->log_message(LogLevel::Warn, format, args...); + } + + template<typename... Args> + static auto error(const std::string_view& format, const Args&... args) -> void + { + auto logger = get_logger(); + if (logger) logger->log_message(LogLevel::Err, format, args...); + } + + template<typename... Args> + static auto fatal(const std::string_view& format, const Args&... args) -> void + { + auto logger = get_logger(); + if (logger) logger->log_message(LogLevel::Fatal, format, args...); + } + + template<typename... Args> + auto log_message(LogLevel level, const std::string_view& format, const Args&... args) -> void + { + if (level < m_log_level) + return; + + std::lock_guard<std::mutex> lock(m_mutex); + std::string message = format_string(format, args...); + std::string full_message = get_time_stamp() + " [" + get_log_level_string(level) + "] " + message; + + if (m_console_output) + { + set_console_color(level); + std::cout << full_message << std::endl; + reset_console_color(); + } + + if (m_file_output && m_log_file.is_open()) + { + m_log_file << full_message << std::endl; + m_log_file.flush(); + } + } + + template<typename... Args> + auto format_string(const std::string_view& format, const Args&... args) -> std::string + { + std::string result = format.data(); + std::vector<std::string> arg_strings = { to_string(args)... }; + + size_t arg_index = 0; + size_t pos = 0; + + while ((pos = result.find("{}", pos)) != std::string::npos && arg_index < arg_strings.size()) + { + result.replace(pos, 2, arg_strings[arg_index]); + pos += arg_strings[arg_index].length(); + arg_index++; + } + + return result; + } + + template<typename T> + auto to_string(const T& value) -> std::string + { + std::stringstream ss; + ss << value; + return ss.str(); + } + + auto set_log_level(LogLevel level) -> void { m_log_level = level; } + auto enable_console_output(bool enable) -> void { m_console_output = enable; } + auto enable_file_output(bool enable) -> void { m_file_output = enable; } + auto set_log_file(const std::string& filename) -> void; + + auto get_time_stamp() -> std::string; + auto get_log_level_string(LogLevel level) -> std::string; + auto set_console_color(LogLevel level) -> void; + auto reset_console_color() -> void; + + private: + LogLevel m_log_level; + bool m_console_output; + bool m_file_output; + std::ofstream m_log_file; + std::mutex m_mutex; + + static Ref<Logger> s_logger; + }; +} + +#if defined(DONUT_DEBUG) + #define DONUT_TRACE(format, ...) ::Donut::Logger::trace(format, ##__VA_ARGS__) + #define DONUT_INFO(format, ...) ::Donut::Logger::info(format, ##__VA_ARGS__) + #define DONUT_WARN(format, ...) ::Donut::Logger::warn(format, ##__VA_ARGS__) + #define DONUT_ERROR(format, ...) ::Donut::Logger::error(format, ##__VA_ARGS__) + #define DONUT_FATAL(format, ...) ::Donut::Logger::fatal(format, ##__VA_ARGS__) +#else + #define DONUT_TRACE(format, ...) {} + #define DONUT_INFO(format, ...) {} + #define DONUT_WARN(format, ...) {} + #define DONUT_ERROR(format, ...) {} + #define DONUT_FATAL(format, ...) {} +#endif diff --git a/src/core/memory.h b/src/core/memory.h new file mode 100644 index 0000000..3240c71 --- /dev/null +++ b/src/core/memory.h @@ -0,0 +1,24 @@ +#pragma once + +#include <memory> + +namespace Donut +{ + template<typename T> + using Ref = std::shared_ptr<T>; + + template<typename T> + using Scope = std::unique_ptr<T>; + + template<typename T, typename ... Args> + constexpr auto create_scope(Args&& ... args) -> Scope<T> + { + return std::make_unique<T>(std::forward<Args>(args)...); + } + + template<typename T, typename ... Args> + constexpr auto create_ref(Args&& ... args) -> Ref<T> + { + return std::make_shared<T>(std::forward<Args>(args)...); + } +}; diff --git a/src/core/settings_manager.cpp b/src/core/settings_manager.cpp new file mode 100644 index 0000000..ca4cac5 --- /dev/null +++ b/src/core/settings_manager.cpp @@ -0,0 +1,200 @@ +#include "settings_manager.h" +#include "log.h" +#include "theme_manager.h" +#include "rendering/renderer.h" + +#include <fstream> +#include <filesystem> +#include <algorithm> + +namespace Donut +{ + Settings SettingsManager::s_settings; + bool SettingsManager::s_initialized = false; + + auto SettingsManager::initialize() -> void + { + if (s_initialized) + return; + + load_settings(); + s_initialized = true; + DONUT_INFO("Settings Manager initialized"); + } + + auto SettingsManager::shutdown() -> void + { + if (!s_initialized) + return; + + save_settings(); + s_initialized = false; + DONUT_INFO("Settings Manager shutdown"); + } + + auto SettingsManager::load_settings() -> void + { + std::string file_path = get_settings_file_path(); + + try + { + if (std::filesystem::exists(file_path)) + { + auto config = toml::parse(file_path); + + if (config.contains("simulation")) + { + auto sim = config["simulation"]; + s_settings.simulation.target_fps = toml::find_or(sim, "target_fps", 60); + s_settings.simulation.compute_height = toml::find_or(sim, "compute_height", 512); + s_settings.simulation.max_steps_moving = toml::find_or(sim, "max_steps_moving", 30000); + s_settings.simulation.max_steps_static = toml::find_or(sim, "max_steps_static", 15000); + s_settings.simulation.early_exit_distance = toml::find_or(sim, "early_exit_distance", 5e12f); + s_settings.simulation.gravity_enabled = toml::find_or(sim, "gravity_enabled", true); + s_settings.simulation.disk_thickness = toml::find_or(sim, "disk_thickness", 0.1f); + s_settings.simulation.disk_density = toml::find_or(sim, "disk_density", 0.1f); + s_settings.simulation.rotation_speed = toml::find_or(sim, "rotation_speed", 1.0f); + s_settings.simulation.blur_strength = toml::find_or(sim, "blur_strength", 2.0f); + s_settings.simulation.glow_intensity = toml::find_or(sim, "glow_intensity", 0.1f); + + s_settings.simulation.target_fps = std::max(30, std::min(120, s_settings.simulation.target_fps)); + s_settings.simulation.compute_height = std::max(64, std::min(2048, s_settings.simulation.compute_height)); + s_settings.simulation.max_steps_moving = std::max(1000, std::min(60000, s_settings.simulation.max_steps_moving)); + s_settings.simulation.max_steps_static = std::max(1000, std::min(30000, s_settings.simulation.max_steps_static)); + s_settings.simulation.early_exit_distance = std::max(1e11f, std::min(1e13f, s_settings.simulation.early_exit_distance)); + s_settings.simulation.disk_thickness = std::max(0.01f, std::min(5.0f, s_settings.simulation.disk_thickness)); + s_settings.simulation.disk_density = std::max(0.01f, std::min(5.0f, s_settings.simulation.disk_density)); + s_settings.simulation.rotation_speed = std::max(0.0f, std::min(5.0f, s_settings.simulation.rotation_speed)); + s_settings.simulation.blur_strength = std::max(0.1f, std::min(10.0f, s_settings.simulation.blur_strength)); + s_settings.simulation.glow_intensity = std::max(0.01f, std::min(5.0f, s_settings.simulation.glow_intensity)); + } + + if (config.contains("graphics")) + { + auto gfx = config["graphics"]; + s_settings.graphics.render_api = toml::find_or(gfx, "render_api", std::string("OpenGL")); + s_settings.graphics.v_sync_enabled = toml::find_or(gfx, "vsync_enabled", true); + s_settings.graphics.show_fps = toml::find_or(gfx, "show_fps", true); + s_settings.graphics.show_performance_metrics = toml::find_or(gfx, "show_performance_metrics", true); + s_settings.graphics.show_debug_info = toml::find_or(gfx, "show_debug_info", false); + s_settings.graphics.enable_anti_aliasing = toml::find_or(gfx, "enable_anti_aliasing", true); + s_settings.graphics.selected_theme = toml::find_or(gfx, "selected_theme", std::string("Dark")); + + if (s_settings.graphics.render_api != "OpenGL" && + s_settings.graphics.render_api != "Vulkan") + s_settings.graphics.render_api = "OpenGL"; + if (s_settings.graphics.selected_theme != "Dark" && + s_settings.graphics.selected_theme != "Light" && + s_settings.graphics.selected_theme != "Blue") + s_settings.graphics.selected_theme = "Dark"; + } + + DONUT_INFO("Settings loaded from {}", file_path); + } + else + { + load_default_settings(); + save_settings(); + DONUT_INFO("No settings file found, created default settings"); + } + } + catch (const std::exception& e) + { + DONUT_ERROR("Failed to load settings: {}", e.what()); + load_default_settings(); + } + } + + auto SettingsManager::save_settings() -> void + { + std::string file_path = get_settings_file_path(); + + try + { + std::filesystem::path path(file_path); + std::filesystem::create_directories(path.parent_path()); + + toml::value simulation = toml::table + { + {"target_fps", s_settings.simulation.target_fps }, + {"compute_height", s_settings.simulation.compute_height }, + {"max_steps_moving", s_settings.simulation.max_steps_moving }, + {"max_steps_static", s_settings.simulation.max_steps_static }, + {"early_exit_distance", s_settings.simulation.early_exit_distance}, + {"gravity_enabled", s_settings.simulation.gravity_enabled }, + {"disk_thickness", s_settings.simulation.disk_thickness }, + {"disk_density", s_settings.simulation.disk_density }, + {"rotation_speed", s_settings.simulation.rotation_speed }, + {"blur_strength", s_settings.simulation.blur_strength }, + {"glow_intensity", s_settings.simulation.glow_intensity } + }; + + toml::value graphics = toml::table + { + {"render_api", s_settings.graphics.render_api }, + {"vsync_enabled", s_settings.graphics.v_sync_enabled }, + {"show_fps", s_settings.graphics.show_fps }, + {"show_performance_metrics", s_settings.graphics.show_performance_metrics}, + {"show_debug_info", s_settings.graphics.show_debug_info }, + {"enable_anti_aliasing", s_settings.graphics.enable_anti_aliasing }, + {"selected_theme", s_settings.graphics.selected_theme } + }; + + toml::value config = toml::table + { + {"simulation", simulation}, + {"graphics", graphics} + }; + + std::ofstream file(file_path); + file << config; + file.close(); + + DONUT_INFO("Settings saved to {}", file_path); + } + catch (const std::exception& e) + { + DONUT_ERROR("Failed to save settings: {}", e.what()); + } + } + + auto SettingsManager::set_simulation_settings(const SimulationSettings& settings) -> void + { + s_settings.simulation = settings; + save_settings(); + } + + auto SettingsManager::set_graphics_settings(const GraphicsSettings& settings) -> void + { + s_settings.graphics = settings; + save_settings(); + } + + auto SettingsManager::get_settings_file_path() -> std::string + { + return "config/settings.toml"; + } + + auto SettingsManager::load_default_settings() -> void + { + s_settings.simulation.target_fps = 60; + s_settings.simulation.compute_height = 512; + s_settings.simulation.max_steps_moving = 30000; + s_settings.simulation.max_steps_static = 15000; + s_settings.simulation.early_exit_distance = 5e12f; + s_settings.simulation.gravity_enabled = true; + s_settings.simulation.disk_thickness = 0.1f; + s_settings.simulation.disk_density = 0.1f; + s_settings.simulation.rotation_speed = 1.0f; + s_settings.simulation.blur_strength = 2.0f; + s_settings.simulation.glow_intensity = 0.1f; + + s_settings.graphics.render_api = "OpenGL"; + s_settings.graphics.v_sync_enabled = true; + s_settings.graphics.show_fps = true; + s_settings.graphics.show_performance_metrics = true; + s_settings.graphics.show_debug_info = false; + s_settings.graphics.enable_anti_aliasing = true; + s_settings.graphics.selected_theme = "Dark"; + } +} diff --git a/src/core/settings_manager.h b/src/core/settings_manager.h new file mode 100644 index 0000000..95826c7 --- /dev/null +++ b/src/core/settings_manager.h @@ -0,0 +1,82 @@ +#pragma once + +#include <string> +#include <toml.hpp> + +namespace Donut +{ + struct SimulationSettings + { + int target_fps = 60; + int compute_height = 512; + int max_steps_moving = 30000; + int max_steps_static = 15000; + float early_exit_distance = 5e12f; + bool gravity_enabled = true; + float disk_thickness = 0.1f; + float disk_density = 0.1f; + float rotation_speed = 1.0f; + float blur_strength = 2.0f; + float glow_intensity = 0.1f; + }; + + struct GraphicsSettings + { + std::string render_api = "OpenGL"; + bool v_sync_enabled = true; + bool show_fps = true; + bool show_performance_metrics = true; + bool show_debug_info = false; + bool enable_anti_aliasing = true; + std::string selected_theme = "Dark"; + }; + + struct Settings + { + SimulationSettings simulation; + GraphicsSettings graphics; + }; + + class SettingsManager + { + public: + static auto initialize() -> void; + static auto shutdown() -> void; + + static auto load_settings() -> void; + static auto save_settings() -> void; + + static auto get_settings() -> Settings& { return s_settings; } + static auto get_settings_const() -> const Settings& { return s_settings; } + + static auto set_simulation_settings(const SimulationSettings& settings) -> void; + static auto set_graphics_settings(const GraphicsSettings& settings) -> void; + + static auto get_target_fps() -> int { return s_settings.simulation.target_fps; } + static auto get_compute_height() -> int { return s_settings.simulation.compute_height; } + static auto get_max_steps_moving() -> int { return s_settings.simulation.max_steps_moving; } + static auto get_max_steps_static() -> int { return s_settings.simulation.max_steps_static; } + static auto get_early_exit_distance() -> float { return s_settings.simulation.early_exit_distance; } + static auto get_gravity_enabled() -> bool { return s_settings.simulation.gravity_enabled; } + static auto get_disk_thickness() -> float { return s_settings.simulation.disk_thickness; } + static auto get_disk_density() -> float { return s_settings.simulation.disk_density; } + static auto get_rotation_speed() -> float { return s_settings.simulation.rotation_speed; } + static auto get_blur_strength() -> float { return s_settings.simulation.blur_strength; } + static auto get_glow_intensity() -> float { return s_settings.simulation.glow_intensity; } + static auto get_render_api() -> std::string { return s_settings.graphics.render_api; } + static auto get_v_sync_enabled() -> bool { return s_settings.graphics.v_sync_enabled; } + static auto get_show_fps() -> bool { return s_settings.graphics.show_fps; } + static auto get_show_performance_metrics() -> bool { return s_settings.graphics.show_performance_metrics; } + static auto get_show_debug_info() -> bool { return s_settings.graphics.show_debug_info; } + static auto get_enable_anti_aliasing() -> bool { return s_settings.graphics.enable_anti_aliasing; } + static auto get_selected_theme() -> std::string { return s_settings.graphics.selected_theme; } + + private: + static auto get_settings_file_path() -> std::string; + static auto load_default_settings() -> void; + + private: + static Settings s_settings; + static bool s_initialized; + }; +} diff --git a/src/core/state.h b/src/core/state.h new file mode 100644 index 0000000..623f1c2 --- /dev/null +++ b/src/core/state.h @@ -0,0 +1,19 @@ +#pragma once + +#include "event.h" + +namespace Donut +{ + class State + { + public: + virtual ~State() = default; + + virtual auto on_enter() -> void = 0; + virtual auto on_exit() -> void = 0; + virtual auto on_update(float delta_time) -> void = 0; + virtual auto on_render() -> void = 0; + virtual auto on_im_ui_render() -> void = 0; + virtual auto on_event(Event& event) -> void = 0; + }; +}; diff --git a/src/core/state_manager.cpp b/src/core/state_manager.cpp new file mode 100644 index 0000000..8018401 --- /dev/null +++ b/src/core/state_manager.cpp @@ -0,0 +1,88 @@ +#include "state_manager.h" +#include "log.h" + +namespace Donut +{ + auto StateManager::shutdown() -> void + { + if (m_current_state) + m_current_state->on_exit(); + + destroy_states(); + DONUT_INFO("StateManager shutdown"); + } + + auto StateManager::destroy_states() -> void + { + m_states.clear(); + m_current_state = nullptr; + m_current_state_name = ""; + } + + auto StateManager::register_state(const std::string& state_name, Scope<State> state) -> void + { + if (m_states.find(state_name) != m_states.end()) + DONUT_WARN("State '{}' already exists, overwriting", state_name); + m_states[state_name] = std::move(state); + DONUT_INFO("Registered state: {}", state_name); + } + + auto StateManager::switch_to_state(const std::string& state_name) -> void + { + auto it = m_states.find(state_name); + if (it == m_states.end()) + { + DONUT_ERROR("Attempted to switch to unknown state: {}", state_name); + return; + } + + State* new_state = it->second.get(); + if (new_state == m_current_state) + return; + + if (m_current_state) + m_current_state->on_exit(); + + m_current_state = new_state; + m_current_state_name = state_name; + m_current_state->on_enter(); + DONUT_INFO("Switched to state: {}", state_name); + } + + auto StateManager::get_state(const std::string& state_name) -> State* + { + auto it = m_states.find(state_name); + if (it != m_states.end()) + return it->second.get(); + return nullptr; + } + + auto StateManager::has_state(const std::string& state_name) const -> bool + { + return m_states.find(state_name) != m_states.end(); + } + + auto StateManager::update(float delta_time) -> void + { + if (m_current_state) + m_current_state->on_update(delta_time); + } + + auto StateManager::render() -> void + { + if (m_current_state) + m_current_state->on_render(); + } + + auto StateManager::on_im_ui_render() -> void + { + if (m_current_state) + m_current_state->on_im_ui_render(); + } + + auto StateManager::on_event(Event& event) -> void + { + if (m_current_state) + m_current_state->on_event(event); + } +} diff --git a/src/core/state_manager.h b/src/core/state_manager.h new file mode 100644 index 0000000..446841e --- /dev/null +++ b/src/core/state_manager.h @@ -0,0 +1,41 @@ +#pragma once + +#include "memory.h" +#include "state.h" + +#include <string> +#include <unordered_map> + +namespace Donut +{ + class StateManager + { + public: + ~StateManager() = default; + auto shutdown() -> void; + + auto update(float delta_time) -> void; + auto render() -> void; + auto on_im_ui_render() -> void; + auto on_event(Event& event) -> void; + + auto register_state(const std::string& state_name, Scope<State> state) -> void; + auto switch_to_state(const std::string& state_name) -> void; + + auto get_current_state_name() const -> std::string { return m_current_state_name; } + auto get_current_state() const -> State* { return m_current_state; } + + auto get_state(const std::string& state_name) -> State*; + auto has_state(const std::string& state_name) const -> bool; + + private: + auto create_states() -> void; + auto destroy_states() -> void; + + private: + State* m_current_state = nullptr; + std::string m_current_state_name = ""; + + std::unordered_map<std::string, Scope<State>> m_states; + }; +} diff --git a/src/core/theme_manager.cpp b/src/core/theme_manager.cpp new file mode 100644 index 0000000..4ae025f --- /dev/null +++ b/src/core/theme_manager.cpp @@ -0,0 +1,291 @@ +#include "theme_manager.h" + +namespace Donut +{ + Theme ThemeManager::s_current_theme = Theme::Dark; + + auto ThemeManager::set_theme(Theme theme) -> void + { + s_current_theme = theme; + + switch (theme) + { + case Theme::Dark: + apply_dark_theme(); + break; + case Theme::Light: + apply_light_theme(); + break; + case Theme::Blue: + apply_blue_theme(); + break; + } + } + + auto ThemeManager::apply_dark_theme() -> void + { + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4* colors = style.Colors; + + colors[ImGuiCol_Text] = ImVec4(0.95f, 0.95f, 0.95f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.95f); + colors[ImGuiCol_ChildBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.95f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.95f); + colors[ImGuiCol_Border] = ImVec4(0.25f, 0.25f, 0.25f, 0.54f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.12f, 0.12f, 0.12f, 0.95f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.18f, 0.18f, 0.18f, 0.95f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.22f, 0.22f, 0.22f, 0.95f); + colors[ImGuiCol_TitleBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.60f, 0.60f, 0.60f, 0.95f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.60f, 0.60f, 0.60f, 0.95f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.70f, 0.70f, 0.70f, 0.95f); + colors[ImGuiCol_Button] = ImVec4(0.15f, 0.15f, 0.15f, 0.95f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.20f, 0.20f, 0.20f, 0.95f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.25f, 0.25f, 0.25f, 0.95f); + colors[ImGuiCol_Header] = ImVec4(0.15f, 0.15f, 0.15f, 0.95f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.20f, 0.20f, 0.20f, 0.95f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.25f, 0.25f, 0.25f, 0.95f); + colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.43f, 0.50f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.29f, 0.30f, 0.31f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.60f, 0.60f, 0.60f, 0.95f); + colors[ImGuiCol_Tab] = ImVec4(0.08f, 0.08f, 0.08f, 0.83f); + colors[ImGuiCol_TabHovered] = ImVec4(0.15f, 0.15f, 0.15f, 0.83f); + colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.08f, 0.08f, 0.08f, 0.97f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.13f, 0.13f, 0.13f, 1.00f); + colors[ImGuiCol_DockingPreview] = ImVec4(0.60f, 0.60f, 0.60f, 0.70f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.60f, 0.60f, 0.60f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + + style.WindowPadding = ImVec2(8, 8); + style.FramePadding = ImVec2(5, 2); + style.CellPadding = ImVec2(6, 4); + style.ItemSpacing = ImVec2(6, 4); + style.ItemInnerSpacing = ImVec2(4, 4); + style.IndentSpacing = 21; + style.ScrollbarSize = 14; + style.GrabMinSize = 10; + + style.WindowBorderSize = 1; + style.ChildBorderSize = 1; + style.PopupBorderSize = 1; + style.FrameBorderSize = 0; + style.TabBorderSize = 0; + + style.WindowRounding = 8.0f; + style.ChildRounding = 6.0f; + style.FrameRounding = 4.0f; + style.PopupRounding = 4.0f; + style.ScrollbarRounding = 4.0f; + style.GrabRounding = 4.0f; + style.TabRounding = 4.0f; + + style.WindowTitleAlign = ImVec2(0.5f, 0.5f); + style.ButtonTextAlign = ImVec2(0.5f, 0.5f); + style.SelectableTextAlign = ImVec2(0.0f, 0.0f); + + style.DisplaySafeAreaPadding = ImVec2(3, 3); + + style.Alpha = 1.0f; + style.DisabledAlpha = 0.6f; + } + + auto ThemeManager::apply_light_theme() -> void + { + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4* colors = style.Colors; + + colors[ImGuiCol_Text] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.95f, 0.95f, 0.95f, 0.95f); + colors[ImGuiCol_ChildBg] = ImVec4(0.92f, 0.92f, 0.92f, 0.95f); + colors[ImGuiCol_PopupBg] = ImVec4(0.95f, 0.95f, 0.95f, 0.95f); + colors[ImGuiCol_Border] = ImVec4(0.70f, 0.70f, 0.70f, 0.54f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.85f, 0.85f, 0.85f, 0.95f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.80f, 0.80f, 0.80f, 0.95f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.75f, 0.75f, 0.75f, 0.95f); + colors[ImGuiCol_TitleBg] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.85f, 0.85f, 0.85f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.95f, 0.95f, 0.95f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.88f, 0.88f, 0.88f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.90f, 0.90f, 0.90f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.20f, 0.60f, 0.90f, 0.95f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.20f, 0.60f, 0.90f, 0.95f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.15f, 0.50f, 0.80f, 0.95f); + colors[ImGuiCol_Button] = ImVec4(0.80f, 0.80f, 0.80f, 0.95f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.75f, 0.75f, 0.75f, 0.95f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 0.95f); + colors[ImGuiCol_Header] = ImVec4(0.85f, 0.85f, 0.85f, 0.95f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.80f, 0.80f, 0.80f, 0.95f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.75f, 0.75f, 0.75f, 0.95f); + colors[ImGuiCol_Separator] = ImVec4(0.70f, 0.70f, 0.70f, 0.50f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.60f, 0.60f, 0.60f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.20f, 0.60f, 0.90f, 0.95f); + colors[ImGuiCol_Tab] = ImVec4(0.85f, 0.85f, 0.85f, 0.83f); + colors[ImGuiCol_TabHovered] = ImVec4(0.80f, 0.80f, 0.80f, 0.83f); + colors[ImGuiCol_TabActive] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.90f, 0.90f, 0.90f, 0.97f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.85f, 0.85f, 0.85f, 1.00f); + colors[ImGuiCol_DockingPreview] = ImVec4(0.20f, 0.60f, 0.90f, 0.70f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.30f, 0.20f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.70f, 0.50f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.80f, 0.40f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.20f, 0.60f, 0.90f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.10f, 0.50f, 0.80f, 1.00f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.20f, 0.60f, 0.90f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.10f, 0.10f, 0.10f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + + style.WindowPadding = ImVec2(8, 8); + style.FramePadding = ImVec2(5, 2); + style.CellPadding = ImVec2(6, 4); + style.ItemSpacing = ImVec2(6, 4); + style.ItemInnerSpacing = ImVec2(4, 4); + style.IndentSpacing = 21; + style.ScrollbarSize = 14; + style.GrabMinSize = 10; + + style.WindowBorderSize = 1; + style.ChildBorderSize = 1; + style.PopupBorderSize = 1; + style.FrameBorderSize = 0; + style.TabBorderSize = 0; + + style.WindowRounding = 8.0f; + style.ChildRounding = 6.0f; + style.FrameRounding = 4.0f; + style.PopupRounding = 4.0f; + style.ScrollbarRounding = 4.0f; + style.GrabRounding = 4.0f; + style.TabRounding = 4.0f; + + style.WindowTitleAlign = ImVec2(0.5f, 0.5f); + style.ButtonTextAlign = ImVec2(0.5f, 0.5f); + style.SelectableTextAlign = ImVec2(0.0f, 0.0f); + + style.DisplaySafeAreaPadding = ImVec2(3, 3); + + style.Alpha = 1.0f; + style.DisabledAlpha = 0.6f; + } + + auto ThemeManager::apply_blue_theme() -> void + { + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4* colors = style.Colors; + + colors[ImGuiCol_Text] = ImVec4(0.95f, 0.95f, 0.95f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.05f, 0.10f, 0.20f, 0.95f); + colors[ImGuiCol_ChildBg] = ImVec4(0.03f, 0.08f, 0.18f, 0.95f); + colors[ImGuiCol_PopupBg] = ImVec4(0.05f, 0.10f, 0.20f, 0.95f); + colors[ImGuiCol_Border] = ImVec4(0.20f, 0.30f, 0.50f, 0.54f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.08f, 0.15f, 0.30f, 0.95f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.40f, 0.95f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.15f, 0.25f, 0.45f, 0.95f); + colors[ImGuiCol_TitleBg] = ImVec4(0.05f, 0.10f, 0.20f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.05f, 0.10f, 0.20f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.08f, 0.15f, 0.25f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.05f, 0.10f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.40f, 0.60f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.50f, 0.70f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.50f, 0.60f, 0.80f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.20f, 0.60f, 1.00f, 0.95f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.20f, 0.60f, 1.00f, 0.95f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.30f, 0.70f, 1.00f, 0.95f); + colors[ImGuiCol_Button] = ImVec4(0.10f, 0.20f, 0.40f, 0.95f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.15f, 0.25f, 0.45f, 0.95f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.20f, 0.30f, 0.50f, 0.95f); + colors[ImGuiCol_Header] = ImVec4(0.10f, 0.20f, 0.40f, 0.95f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.15f, 0.25f, 0.45f, 0.95f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.20f, 0.30f, 0.50f, 0.95f); + colors[ImGuiCol_Separator] = ImVec4(0.30f, 0.40f, 0.60f, 0.50f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.40f, 0.50f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.50f, 0.60f, 0.80f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.30f, 0.40f, 0.60f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.20f, 0.60f, 1.00f, 0.95f); + colors[ImGuiCol_Tab] = ImVec4(0.08f, 0.15f, 0.25f, 0.83f); + colors[ImGuiCol_TabHovered] = ImVec4(0.12f, 0.20f, 0.35f, 0.83f); + colors[ImGuiCol_TabActive] = ImVec4(0.15f, 0.25f, 0.40f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.05f, 0.10f, 0.20f, 0.97f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.08f, 0.15f, 0.25f, 1.00f); + colors[ImGuiCol_DockingPreview] = ImVec4(0.20f, 0.60f, 1.00f, 0.70f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.05f, 0.10f, 0.20f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.60f, 0.70f, 0.90f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.60f, 0.40f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.30f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.80f, 0.40f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.20f, 0.60f, 1.00f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.10f, 0.50f, 0.90f, 1.00f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.20f, 0.60f, 1.00f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + + style.WindowPadding = ImVec2(8, 8); + style.FramePadding = ImVec2(5, 2); + style.CellPadding = ImVec2(6, 4); + style.ItemSpacing = ImVec2(6, 4); + style.ItemInnerSpacing = ImVec2(4, 4); + style.IndentSpacing = 21; + style.ScrollbarSize = 14; + style.GrabMinSize = 10; + + style.WindowBorderSize = 1; + style.ChildBorderSize = 1; + style.PopupBorderSize = 1; + style.FrameBorderSize = 0; + style.TabBorderSize = 0; + + style.WindowRounding = 8.0f; + style.ChildRounding = 6.0f; + style.FrameRounding = 4.0f; + style.PopupRounding = 4.0f; + style.ScrollbarRounding = 4.0f; + style.GrabRounding = 4.0f; + style.TabRounding = 4.0f; + + style.WindowTitleAlign = ImVec2(0.5f, 0.5f); + style.ButtonTextAlign = ImVec2(0.5f, 0.5f); + style.SelectableTextAlign = ImVec2(0.0f, 0.0f); + + style.DisplaySafeAreaPadding = ImVec2(3, 3); + + style.Alpha = 1.0f; + style.DisabledAlpha = 0.6f; + } +} diff --git a/src/core/theme_manager.h b/src/core/theme_manager.h new file mode 100644 index 0000000..d067906 --- /dev/null +++ b/src/core/theme_manager.h @@ -0,0 +1,27 @@ +#pragma once + +#include <imgui.h> + +namespace Donut +{ + enum class Theme + { + Dark = 0, + Light = 1, + Blue = 2 + }; + + class ThemeManager + { + public: + static auto set_theme(Theme theme) -> void; + static auto get_current_theme() -> Theme { return s_current_theme; } + + static auto apply_dark_theme() -> void; + static auto apply_light_theme() -> void; + static auto apply_blue_theme() -> void; + + private: + static Theme s_current_theme; + }; +} diff --git a/src/core/window.cpp b/src/core/window.cpp new file mode 100644 index 0000000..047ef0d --- /dev/null +++ b/src/core/window.cpp @@ -0,0 +1,341 @@ +#include <glad/glad.h> +#include "window.h" +#include "theme_manager.h" +#include "rendering/renderer.h" // RendererAPI::get_api() + +#include <cstdint> + +#include <imgui.h> +#include <imgui_impl_glfw.h> +#include <imgui_impl_opengl3.h> + +namespace Donut +{ + static bool s_glfw_initialized = false; + 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_is_closed(false) + { + init(); + } + + Window::~Window() + { + shutdown(); + } + + auto Window::init() -> void + { + DONUT_INFO("Initializing window: ", m_title, " (", m_width, "x", m_height, ")"); + + if (!s_glfw_initialized) + { + int success = glfwInit(); + if (!success) + { + DONUT_ERROR("Could not initialize GLFW!"); + return; + } + + s_glfw_initialized = true; + DONUT_INFO("GLFW initialized successfully"); + } + + if (RendererAPI::get_api() == RendererAPI::API::Vulkan) + { + // Vulkan manages presentation itself; GLFW must not create a GL context. + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + } + else + { +#ifdef __APPLE__ + // macOS only exposes OpenGL up to 4.1 Core Profile, and requires a + // forward-compatible core-profile context for any modern (>= 3.3) + // shader to compile. Without these hints GLFW hands back a legacy + // 2.1 context and every GLSL shader in the project fails to build. + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); +#endif + } + + m_window = glfwCreateWindow(m_width, m_height, m_title.c_str(), nullptr, nullptr); + if (!m_window) + { + DONUT_ERROR("Could not create GLFW window!"); + glfwTerminate(); + return; + } + + DONUT_INFO("GLFW window created successfully"); + + if (RendererAPI::get_api() != RendererAPI::API::Vulkan) + 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++; + } + + auto Window::shutdown() -> void + { + DONUT_INFO("Shutting down window: ", m_title); + + shutdown_im_gui(); + + glfwDestroyWindow(m_window); + s_glfw_window_count--; + + if (s_glfw_window_count == 0) + { + glfwTerminate(); + s_glfw_initialized = false; + DONUT_INFO("GLFW terminated (no more windows)"); + } + } + + auto Window::on_update() const -> void + { + glfwPollEvents(); + if (RendererAPI::get_api() != RendererAPI::API::Vulkan) + glfwSwapBuffers(m_window); // Vulkan presents via the swapchain instead + } + + auto Window::should_close() const -> bool + { + return glfwWindowShouldClose(m_window) || m_is_closed; + } + + auto Window::set_cursor_locked(bool locked) -> void + { + m_cursor_locked = locked; + glfwSetInputMode(m_window, GLFW_CURSOR, locked ? + GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); + } + + auto Window::set_cursor_visible(bool visible) -> void + { + m_cursor_visible = visible; + glfwSetInputMode(m_window, GLFW_CURSOR, visible ? + GLFW_CURSOR_NORMAL : GLFW_CURSOR_HIDDEN); + } + + auto Window::init_im_gui() -> void + { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; +#ifndef __APPLE__ + // Multi-viewport (dragging ImGui panels out as separate OS windows) + // relies on a populated platform-monitor list and is unreliable on + // macOS, where it intermittently asserts (Monitors.Size > 0) and + // aborts. Docking stays enabled; panels just remain inside the window. + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; +#endif + + setup_im_gui_fonts(); + ThemeManager::set_theme(Theme::Dark); + + ImGuiStyle& style = ImGui::GetStyle(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + style.WindowRounding = 0.0f; + style.Colors[ImGuiCol_WindowBg].w = 1.0f; + } + + ImGui_ImplGlfw_InitForOpenGL(m_window, true); +#ifdef __APPLE__ + // macOS uses a core-profile context, which rejects the legacy + // "#version 130" GLSL the ImGui backend defaults to. 150 is the + // minimum core-profile GLSL that macOS's OpenGL 4.1 accepts. + ImGui_ImplOpenGL3_Init("#version 150"); +#else + ImGui_ImplOpenGL3_Init("#version 130"); +#endif + + DONUT_INFO("ImGUI initialized successfully"); + } + + auto Window::setup_im_gui_fonts() -> void + { + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + + m_main_font = io.Fonts->AddFontFromFileTTF("assets/fonts/inter/static/Inter_18pt-Regular.ttf", 16.0f); + if (!m_main_font) + { + DONUT_WARN("Failed to load Inter font, falling back to default"); + m_main_font = io.Fonts->AddFontDefault(); + } + else + DONUT_INFO("Successfully loaded Inter font"); + + m_large_font = io.Fonts->AddFontFromFileTTF("assets/fonts/inter/static/Inter_18pt-Bold.ttf", 20.0f); + if (!m_large_font) + m_large_font = m_main_font; + + m_small_font = io.Fonts->AddFontFromFileTTF("assets/fonts/inter/static/Inter_18pt-Light.ttf", 12.0f); + if (!m_small_font) + m_small_font = m_main_font; + + io.FontDefault = m_main_font; + DONUT_INFO("ImGUI fonts loaded successfully"); + } + + auto Window::shutdown_im_gui() -> void + { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + DONUT_INFO("ImGUI shutdown"); + } + + auto Window::begin_im_gui_frame() -> void + { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + } + + auto Window::end_im_gui_frame() -> void + { + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + GLFWwindow* backup_current_context = glfwGetCurrentContext(); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + glfwMakeContextCurrent(backup_current_context); + } + } + + auto Window::glfw_error_callback(int error, const char* description) -> void + { + DONUT_ERROR("GLFW Error ({}): {}", error, description ? description : ""); + } + + auto Window::glfw_window_close_callback(GLFWwindow* window) -> void + { + Window* win = static_cast<Window*>(glfwGetWindowUserPointer(window)); + WindowCloseEvent event; + win->m_event_handler.on_event(event); + } + + auto Window::glfw_window_size_callback(GLFWwindow* window, int width, int height) -> void + { + Window* win = static_cast<Window*>(glfwGetWindowUserPointer(window)); + win->m_width = width; + win->m_height = height; + + WindowResizeEvent event(width, height); + win->m_event_handler.on_event(event); + } + + auto Window::glfw_window_focus_callback(GLFWwindow* window, int focused) -> void + { + Window* win = static_cast<Window*>(glfwGetWindowUserPointer(window)); + + if (focused) + { + WindowFocusEvent event; + win->m_event_handler.on_event(event); + } + else + { + WindowLostFocusEvent event; + win->m_event_handler.on_event(event); + } + } + + auto Window::glfw_window_pos_callback(GLFWwindow* window, int xpos, int ypos) -> void + { + Window* win = static_cast<Window*>(glfwGetWindowUserPointer(window)); + WindowMovedEvent event(xpos, ypos); + win->m_event_handler.on_event(event); + } + + auto Window::glfw_key_callback(GLFWwindow* window, int key, int scancode, + int action, int mods) -> void + { + Window* win = static_cast<Window*>(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; + } + } + + auto Window::glfw_char_callback(GLFWwindow* window, unsigned int keycode) -> void + { + Window* win = static_cast<Window*>(glfwGetWindowUserPointer(window)); + KeyTypedEvent event(keycode); + win->m_event_handler.on_event(event); + } + + auto Window::glfw_mouse_button_callback(GLFWwindow* window, int button, + int action, int mods) -> void + { + Window* win = static_cast<Window*>(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; + } + } + + auto Window::glfw_mouse_scroll_callback(GLFWwindow* window, double x_offset, double y_offset) -> void + { + Window* win = static_cast<Window*>(glfwGetWindowUserPointer(window)); + MouseScrolledEvent event((float)x_offset, (float)y_offset); + win->m_event_handler.on_event(event); + } + + auto Window::glfw_cursor_pos_callback(GLFWwindow* window, double x_pos, double y_pos) -> void + { + Window* win = static_cast<Window*>(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 new file mode 100644 index 0000000..9b7d7b9 --- /dev/null +++ b/src/core/window.h @@ -0,0 +1,78 @@ +#pragma once + +#include "event.h" +#include "log.h" + +#include <GLFW/glfw3.h> +#include <imgui.h> + +#include <string> + +namespace Donut +{ + class Window + { + public: + using EventCallbackFn = std::function<void(Event&)>; + + Window(const std::string& title, int width, int height); + ~Window(); + + auto should_close() const -> bool; + auto on_update() const -> void; + + auto set_event_callback(const EventCallbackFn& callback) -> void + { + m_event_handler.set_event_callback(callback); + } + + auto get_event_handler() -> EventHandler& { return m_event_handler; } + + auto get_width() const -> unsigned int { return m_width; } + auto get_height() const -> unsigned int { return m_height; } + + auto get_native_window() const -> void* { return m_window; } + + auto set_cursor_locked(bool locked) -> void; + auto set_cursor_visible(bool visible) -> void; + auto is_cursor_locked() const -> bool { return m_cursor_locked; } + auto is_cursor_visible() const -> bool { return m_cursor_visible; } + + auto init_im_gui() -> void; + auto begin_im_gui_frame() -> void; + auto end_im_gui_frame() -> void; + + private: + auto init() -> void; + auto shutdown() -> void; + + auto setup_im_gui_fonts() -> void; + auto shutdown_im_gui() -> void; + + static auto glfw_error_callback(int error, const char* description) -> void; + static auto glfw_window_close_callback(GLFWwindow* window) -> void; + static auto glfw_window_size_callback(GLFWwindow* window, int width, int height) -> void; + static auto glfw_window_focus_callback(GLFWwindow* window, int focused) -> void; + static auto glfw_window_pos_callback(GLFWwindow* window, int xpos, int ypos) -> void; + static auto glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) -> void; + static auto glfw_char_callback(GLFWwindow* window, unsigned int keycode) -> void; + static auto glfw_mouse_button_callback(GLFWwindow* window, int button, int action, int mods) -> void; + static auto glfw_mouse_scroll_callback(GLFWwindow* window, double x_offset, double y_offset) -> void; + static auto glfw_cursor_pos_callback(GLFWwindow* window, double x_pos, double y_pos) -> void; + + private: + GLFWwindow* m_window; + EventHandler m_event_handler; + + std::string m_title; + unsigned int m_width; + unsigned int m_height; + bool m_is_closed; + bool m_cursor_locked = false; + bool m_cursor_visible = true; + + ImFont* m_main_font = nullptr; + ImFont* m_small_font = nullptr; + ImFont* m_large_font = nullptr; + }; +} |
