aboutsummaryrefslogtreecommitdiff
path: root/src/core/window.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/window.cpp')
-rw-r--r--src/core/window.cpp341
1 files changed, 341 insertions, 0 deletions
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);
+ }
+}