diff --git a/src/display/glfw/Window.cpp b/src/display/glfw/Window.cpp new file mode 100644 index 0000000..ef97ca2 --- /dev/null +++ b/src/display/glfw/Window.cpp @@ -0,0 +1,180 @@ +#include "Window.hpp" + +#include + +namespace display { + + Window::Window(int width, int height, std::string_view title, GLFWmonitor* monitor, GLFWwindow* share) { + // Create the native GLFW window (string_view needs null-terminated C-string conversion) + handle = glfwCreateWindow(width, height, title.data(), monitor, share); + if (!handle) return; + + // Attach 'this' pointer to the GLFW user pointer field. + // This allows static C callbacks to retrieve the C++ class instance later. + glfwSetWindowUserPointer(handle, this); + } + + Window::~Window() noexcept { + if (handle) { + // Clear user pointer prior to destruction to avoid dangling references during teardown + glfwSetWindowUserPointer(handle, nullptr); + glfwDestroyWindow(handle); + handle = nullptr; + } + } + + Window::Window(Window&& other) noexcept : handle(std::exchange(other.handle, nullptr)), callbacks(std::move(other.callbacks)) { + + // Update the native handle's user pointer to point to the newly moved instance + if (handle) glfwSetWindowUserPointer(handle, this); + } + + Window& Window::operator=(Window&& other) noexcept { + if (this != &other) { + // Free current window resource before taking over the new one + if (handle) glfwDestroyWindow(handle); + + // Steal handle and move state + handle = std::exchange(other.handle, nullptr); + callbacks = std::move(other.callbacks); + + // Update user pointer to new host object + if (handle) glfwSetWindowUserPointer(handle, this); + } + return *this; + } + + bool Window::isValid() const noexcept { + return handle != nullptr; + } + + bool Window::shouldClose() const noexcept { + return handle ? glfwWindowShouldClose(handle) : true; + } + + void Window::setShouldClose(bool value) noexcept { + if (!handle) return; + glfwSetWindowShouldClose(handle, value ? GLFW_TRUE : GLFW_FALSE); + } + + void Window::swapBuffers() noexcept { + if (!handle) return; + glfwSwapBuffers(handle); + } + + void Window::makeContextCurrent() noexcept { + if (!handle) return; + glfwMakeContextCurrent(handle); + } + + std::pair Window::getSize() const noexcept { + int w = 0, h = 0; + if (handle) glfwGetWindowSize(handle, &w, &h); + return { w, h }; + } + + std::pair Window::getFramebufferSize() const noexcept { + int w = 0, h = 0; + if (handle) glfwGetFramebufferSize(handle, &w, &h); + return { w, h }; + } + + void Window::setTitle(std::string_view title) { + if (!handle) return; + glfwSetWindowTitle(handle, title.data()); + } + + void Window::setVisible(bool val) noexcept { + if(val) glfwShowWindow(handle); + else glfwHideWindow(handle); + } + + + GLFWwindow* Window::getNativeHandle() const noexcept { + return handle; + } + + Window::operator GLFWwindow* () const noexcept { + return handle; + } + + // --- CALLBACKS --- // + + void Window::setResizeCallback(ResizeFn fn) { + if (!handle) return; + callbacks.onResize = std::move(fn); + glfwSetFramebufferSizeCallback(handle, callbacks.onResize ? Window::glfwFramebufferSizeBridge : nullptr); + } + + void Window::setKeyCallback(KeyFn fn) { + if (!handle) return; + callbacks.onKey = std::move(fn); + glfwSetKeyCallback(handle, callbacks.onKey ? Window::glfwKeyBridge : nullptr); + } + + void Window::setCloseCallback(CloseFn fn) { + if (!handle) return; + callbacks.onClose = std::move(fn); + glfwSetWindowCloseCallback(handle, callbacks.onClose ? Window::glfwWindowCloseBridge : nullptr); + } + + void Window::setCursorPosCallback(CursorPosFn fn) { + if (!handle) return; + callbacks.onCursorPos = std::move(fn); + glfwSetCursorPosCallback(handle, callbacks.onCursorPos ? Window::glfwCursorPosBridge : nullptr); + } + + void Window::setMouseButtonCallback(MouseButtonFn fn) { + if (!handle) return; + callbacks.onMouseButton = std::move(fn); + glfwSetMouseButtonCallback(handle, callbacks.onMouseButton ? Window::glfwMouseButtonBridge : nullptr); + } + + void Window::setScrollCallback(ScrollFn fn) { + if (!handle) return; + callbacks.onScroll = std::move(fn); + glfwSetScrollCallback(handle, callbacks.onScroll ? Window::glfwScrollBridge : nullptr); + } + + void Window::setCursorEnterCallback(CursorEnterFn fn) { + if (!handle) return; + callbacks.onCursorEnter = std::move(fn); + glfwSetCursorEnterCallback(handle, callbacks.onCursorEnter ? Window::glfwCursorEnterBridge : nullptr); + } + + void Window::glfwFramebufferSizeBridge(GLFWwindow* handle, int width, int height) { + auto* self = static_cast(glfwGetWindowUserPointer(handle)); + if (self && self->callbacks.onResize) self->callbacks.onResize(width, height); + } + + void Window::glfwWindowCloseBridge(GLFWwindow* handle) { + auto* self = static_cast(glfwGetWindowUserPointer(handle)); + if (self && self->callbacks.onClose) self->callbacks.onClose(); + } + + void Window::glfwKeyBridge(GLFWwindow* handle, int key, int scancode, int action, int mods) { + auto* self = static_cast(glfwGetWindowUserPointer(handle)); + if (self && self->callbacks.onKey) self->callbacks.onKey(key, scancode, action, mods); + } + + void Window::glfwCursorPosBridge(GLFWwindow* handle, double xpos, double ypos) { + auto* self = static_cast(glfwGetWindowUserPointer(handle)); + if (self && self->callbacks.onCursorPos) self->callbacks.onCursorPos(xpos, ypos); + } + + void Window::glfwMouseButtonBridge(GLFWwindow* handle, int button, int action, int mods) { + auto* self = static_cast(glfwGetWindowUserPointer(handle)); + if (self && self->callbacks.onMouseButton) self->callbacks.onMouseButton(button, action, mods); + } + + void Window::glfwScrollBridge(GLFWwindow* handle, double xoffset, double yoffset) { + auto* self = static_cast(glfwGetWindowUserPointer(handle)); + if (self && self->callbacks.onScroll) self->callbacks.onScroll(xoffset, yoffset); + } + + void Window::glfwCursorEnterBridge(GLFWwindow* handle, int entered) { + auto* self = static_cast(glfwGetWindowUserPointer(handle)); + if (self && self->callbacks.onCursorEnter) self->callbacks.onCursorEnter(entered == GLFW_TRUE); + } + +} \ No newline at end of file diff --git a/src/display/glfw/Window.hpp b/src/display/glfw/Window.hpp new file mode 100644 index 0000000..45741f5 --- /dev/null +++ b/src/display/glfw/Window.hpp @@ -0,0 +1,174 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace display { + + /** + * Represents a window backed by GLFW. + * This abstraction considers this to be a physical + * window on the user's desktop. + */ + class Window { + public: + + using ResizeFn = std::function; + using CloseFn = std::function; + + using KeyFn = std::function; + + using CursorPosFn = std::function; + using MouseButtonFn = std::function; + using ScrollFn = std::function; + using CursorEnterFn = std::function; + + struct Callbacks { + + ResizeFn onResize{ nullptr }; + CloseFn onClose{ nullptr }; + + KeyFn onKey{ nullptr }; + + CursorPosFn onCursorPos{ nullptr }; + MouseButtonFn onMouseButton{ nullptr }; + ScrollFn onScroll{ nullptr }; + CursorEnterFn onCursorEnter{ nullptr }; + + }; + + private: + + GLFWwindow* handle{ nullptr }; + Callbacks callbacks{ nullptr }; + + public: + + /** + * @brief Constructs an uninitialized, null Window wrapper. + */ + Window() noexcept = default; + + /** + * @brief Constructs and initializes a physical GLFW window. + * + * @param width Initial width in screen coordinates. + * @param height Initial height in screen coordinates. + * @param title Title displayed in the window header bar. + * @param monitor Monitor to use for full screen, or nullptr for windowed mode. + * @param share Window whose context to share resources with, or nullptr. + * + * @throws std::runtime_error If window creation fails. + */ + Window(int width, int height, std::string_view title, GLFWmonitor* monitor = nullptr, GLFWwindow* share = nullptr); + + /** + * @brief Destructor. Destroys the underlying GLFW window handle if valid. + */ + ~Window() noexcept; + + // --- Prevent Copying (RAII Single-Ownership) --- + Window(const Window&) = delete; + + Window& operator=(const Window&) = delete; + + // --- Enable Move Semantics --- + /** + * @brief Move constructor. Takes ownership of another Window's handle. + */ + Window(Window&& other) noexcept; + + public: + + /** + * @brief Move assignment operator. Safely cleans up current handle and steals other's handle. + */ + Window& operator=(Window&& other) noexcept; + + public: + + // --- Core Operations --- + + /** + * @brief Checks if the underlying GLFW handle is non-null. + */ + bool isValid() const noexcept; + + /** + * @brief Returns true if the user or system requested this window to close. + */ + bool shouldClose() const noexcept; + + /** + * @brief Sets the close flag for the window. + */ + void setShouldClose(bool value) noexcept; + + /** + * @brief Swaps the front and back color buffers. + */ + void swapBuffers() noexcept; + + void setVisible(bool val) noexcept; + + /** + * @brief Makes this window's OpenGL context current on the calling thread. + */ + void makeContextCurrent() noexcept; + + // --- Window State Query & Mutators --- + + std::pair getSize() const noexcept; + + std::pair getFramebufferSize() const noexcept; + + void setTitle(std::string_view title); + + public: // --- Callbacks --- + + /** + * @brief Attaches a modern C++ callback for window resize events. + */ + void setResizeCallback(ResizeFn callback); + + void setCloseCallback(CloseFn callback); + + void setKeyCallback(KeyFn callback); + + void setCursorPosCallback(CursorPosFn fn); + + void setMouseButtonCallback(MouseButtonFn fn); + + void setScrollCallback(ScrollFn fn); + + void setCursorEnterCallback(CursorEnterFn fn); + + public: // --- Native Accessors --- + + /** + * @brief Returns the raw, underlying GLFWwindow pointer for low-level or library integration. + */ + GLFWwindow* getNativeHandle() const noexcept; + + /** + * @brief Implicit conversion operator to GLFWwindow* for ergonomic API interoperability. + */ + operator GLFWwindow* () const noexcept; + + private: + + static void glfwFramebufferSizeBridge(GLFWwindow* handle, int width, int height); + static void glfwWindowCloseBridge(GLFWwindow* handle); + static void glfwKeyBridge(GLFWwindow* handle, int key, int scancode, int action, int mods); + static void glfwCursorPosBridge(GLFWwindow* handle, double xpos, double ypos); + static void glfwMouseButtonBridge(GLFWwindow* handle, int button, int action, int mods); + static void glfwScrollBridge(GLFWwindow* handle, double xoffset, double yoffset); + static void glfwCursorEnterBridge(GLFWwindow* handle, int entered); + + }; + +}