#pragma once #include #include "../Base/UI/BaseWindow.hpp" #include "../Base/Logger.hpp" namespace openVulkanoCpp { class WindowGLFW : public BaseWindow, virtual public IVulkanWindow, virtual public IOpenGlWindow { private: GLFWwindow* window = nullptr; IWindowHandler* handler = nullptr; public: WindowGLFW() = default; virtual ~WindowGLFW() override { if (window != nullptr) Close(); } protected: GLFWmonitor* GetCurrentMonitor() const { int monitorCount = 0; GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); for(int i = 0; i < monitorCount; i++) { int posX, posY, sizeX, sizeY; glfwGetMonitorWorkarea(monitors[i], &posX, &posY, &sizeX, &sizeY); if (windowConfig.posX >= posX && windowConfig.posX < posX + sizeX && windowConfig.posY >= posY && windowConfig.posY < posY + sizeY) { return monitors[i]; } } return nullptr; } GLFWmonitor* GetTargetMonitor() const { if(windowConfig.windowMode == FULLSCREEN) { // TODO add config to control used display return GetPrimaryMonitor(); } return nullptr; } void Create() { glfwWindowHint(GLFW_DECORATED, (~windowConfig.windowMode) & 1); //TODO handle full screen resolutions window = glfwCreateWindow(windowConfig.width, windowConfig.height, windowConfig.title.c_str(), GetTargetMonitor(), nullptr); if(!window) return; glfwSetWindowUserPointer(window, this); RegisterCallbacks(); } void RegisterCallbacks() const { glfwSetErrorCallback(ErrorCallback); glfwSetDropCallback(window, DropCallback); glfwSetFramebufferSizeCallback(window, ResizeCallback); glfwSetWindowFocusCallback(window, FocusCallback); glfwSetWindowRefreshCallback(window, RefreshCallback); glfwSetWindowIconifyCallback(window, MinimizeCallback); glfwSetWindowPosCallback(window, WindowMoveCallback); glfwSetWindowCloseCallback(window, CloseCallback); // Input Callbacks glfwSetKeyCallback(window, KeyboardCallback); glfwSetMouseButtonCallback(window, MouseButtonCallback); glfwSetCursorPosCallback(window, MouseMoveCallback); glfwSetScrollCallback(window, MouseScrollCallback); } static GLFWmonitor* GetPrimaryMonitor() { return glfwGetPrimaryMonitor(); } static std::vector GetMonitors() { int count; GLFWmonitor** monitorsArray = glfwGetMonitors(&count); std::vector monitors; monitors.reserve(count); for (int i = 0; i < count; i++) { monitors[i] = monitorsArray[i]; } return monitors; } public: // IWindow implementation void Init(RenderAPI::RenderApi renderApi) override { if (!glfwInit()) throw WindowInitFailedException("Failed to initialize glfw"); if(renderApi == RenderAPI::VULKAN) glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); Create(); if(!window) { glfwTerminate(); throw WindowInitFailedException("Failed to initialize window"); } if (renderApi != RenderAPI::VULKAN) MakeCurrentThread(); Logger::WINDOW->info("GLFW Window created (id: {0})", GetWindowId()); } void Close() override { glfwDestroyWindow(window); window = nullptr; glfwTerminate(); Logger::WINDOW->info("GLFW Window destroyed (id: {0})", GetWindowId()); } void Present() const override { glfwSwapBuffers(window); } void Show() override { glfwShowWindow(window); } void Hide() override { glfwHideWindow(window); } void Tick() override { glfwPollEvents(); } void SetTitle(const std::string& title) override { windowConfig.title = title; glfwSetWindowTitle(window, title.c_str()); } void SetSize(uint32_t width, uint32_t height) override { windowConfig.width = width; windowConfig.height = height; if (window) { glfwSetWindowSize(window, width, height); } } void SetPosition(int posX, int posY) override { windowConfig.posX = posX; windowConfig.posY = posY; if(window) { glfwSetWindowPos(window, posX, posY); } } void SetSizeLimits(int minWidth, int minHeight, int maxWidth, int maxHeight) override { minWidth = (minWidth < 0) ? GLFW_DONT_CARE : minWidth; minHeight = (minHeight < 0) ? GLFW_DONT_CARE : minHeight; maxWidth = (maxWidth < 0) ? GLFW_DONT_CARE : maxWidth; maxHeight = (maxHeight < 0) ? GLFW_DONT_CARE : maxHeight; glfwSetWindowSizeLimits(window, minWidth, minHeight, maxWidth, maxHeight); } void MakeCurrentThread() override { glfwMakeContextCurrent(window); } void SetWindowMode(WindowMode windowMode) override { if (windowMode == this->windowConfig.windowMode) return; // Nothing to change here if (!window) return; // Window not yet created this->windowConfig.windowMode = windowMode; uint32_t sizeX = 0, sizeY = 0; int posX = 0, posY = 0; GLFWmonitor* monitor = nullptr; glfwWindowHint(GLFW_DECORATED, (~windowMode) & 1); if(windowMode == WINDOWED || windowMode == BORDERLESS) { sizeX = windowConfig.width; sizeY = windowConfig.height; posX = windowConfig.posX; posY = windowConfig.posY; monitor = nullptr; if(windowMode == WINDOWED) { Logger::WINDOW->info("Switching to Windowed mode"); } else { Logger::WINDOW->info("Switching to Borderless Windowed mode"); } } else { // Fullscreen GetPosition(&windowConfig.posX, &windowConfig.posY); // Backup current window position monitor = GetCurrentMonitor(); if (!monitor) monitor = GetPrimaryMonitor(); if (!monitor) return; // We don't have a monitor to set the fullscreen window to const GLFWvidmode* videoMode = glfwGetVideoMode(monitor ? monitor : GetPrimaryMonitor()); sizeX = videoMode->width; sizeY = videoMode->height; if (windowMode == FULLSCREEN) { // TODO find video mode that best matches user settings Logger::WINDOW->info("Switching to Fullscreen mode"); } else if (windowMode == BORDERLESS_FULLSCREEN) { int sX, sY; glfwGetMonitorWorkarea(monitor, &posX, &posY, &sX, &sY); monitor = nullptr; Logger::WINDOW->info("Switching to Borderless Fullscreen mode"); } } glfwSetWindowMonitor(this->window, monitor, posX, posY, sizeX, sizeY, GLFW_DONT_CARE); } void SetWindowHandler(IWindowHandler* handler) override { this->handler = handler; } IVulkanWindow* GetVulkanWindow() override { return this; } IOpenGlWindow* GetOpenGlWindow() override { return this; } // Status getter void GetSize(int* width, int* height) override { glfwGetWindowSize(window, width, height); } void GetPosition(int* x, int* y) override { glfwGetWindowPos(window, x, y); } IWindowHandler* GetWindowHandler() override { return handler; } //IVulkanWindow stuff vk::SurfaceKHR CreateSurface(const vk::Instance& instance, const vk::AllocationCallbacks* pAllocator) override { VkSurfaceKHR rawSurface; const auto result = static_cast(glfwCreateWindowSurface(static_cast(instance), window, reinterpret_cast(pAllocator), &rawSurface)); return createResultValue(result, rawSurface, "vk::CommandBuffer::begin"); } std::vector GetRequiredInstanceExtensions() override { return GetVulkanRequiredInstanceExtensions(); } public: // Window events void OnResize(const uint32_t newWidth, const uint32_t newHeight) { Logger::WINDOW->debug("Window (id: {0}) resized (width: {1}, height: {2})", GetWindowId(), newWidth, newHeight); handler->OnWindowResize(this, newWidth, newHeight); } void OnMinimize() { Logger::WINDOW->debug("Window (id: {0}) minimized", GetWindowId()); handler->OnWindowMinimize(this); } void OnRestore() { Logger::WINDOW->debug("Window (id: {0}) restored", GetWindowId()); handler->OnWindowRestore(this); } void OnFocusLost() { Logger::WINDOW->debug("Window (id: {0}) focus lost", GetWindowId()); handler->OnWindowFocusLost(this); } void OnFocusGained() { Logger::WINDOW->debug("Window (id: {0}) focus gained", GetWindowId()); handler->OnWindowFocusGained(this); } void OnMove(const int posX, const int posY) { Logger::WINDOW->debug("Window (id: {0}) moved (x: {1}, y: {2})", GetWindowId(), posX, posY); if (windowConfig.windowMode == WINDOWED || windowConfig.windowMode == BORDERLESS) { // Don't save window position for fullscreen windowConfig.posX = posX; windowConfig.posY = posY; } handler->OnWindowMove(this, posX, posY); } void OnClose() { Logger::WINDOW->debug("Window (id: {0}) closed", GetWindowId()); handler->OnWindowClose(this); } public: // Input events TODO virtual void OnKeyPressed(int key, int mods) {} virtual void OnKeyReleased(int key, int mods) {} virtual void OnMousePressed(int button, int mods) {} virtual void OnMouseReleased(int button, int mods) {} virtual void OnMouseMoved(double posX, double posY) {} virtual void OnMouseScrolled(double delta) {} protected: virtual void OnKeyEvent(int key, int scanCode, int action, int mods) { if (key == GLFW_KEY_ENTER && action == GLFW_PRESS && mods == GLFW_MOD_ALT) { WindowMode newMode = FULLSCREEN; switch (windowConfig.windowMode) { case WINDOWED: newMode = FULLSCREEN; break; case BORDERLESS: newMode = BORDERLESS_FULLSCREEN; break; case FULLSCREEN: newMode = WINDOWED; break; case BORDERLESS_FULLSCREEN: newMode = BORDERLESS; break; } SetWindowMode(newMode); } switch (action) { case GLFW_PRESS: OnKeyPressed(key, mods); break; case GLFW_RELEASE: OnKeyReleased(key, mods); break; default: break; } } virtual void OnMouseButtonEvent(int button, int action, int mods) { switch (action) { case GLFW_PRESS: OnMousePressed(button, mods); break; case GLFW_RELEASE: OnMouseReleased(button, mods); break; default: break; } } private: // Callbacks static WindowGLFW* GetWindow(GLFWwindow* window) { return static_cast(glfwGetWindowUserPointer(window)); } static void KeyboardCallback(GLFWwindow* window, int key, int scanCode, int action, int mods) { GetWindow(window)->OnKeyEvent(key, scanCode, action, mods); } static void MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { GetWindow(window)->OnMouseButtonEvent(button, action, mods); } static void MouseMoveCallback(GLFWwindow* window, double posX, double posY) { GetWindow(window)->OnMouseMoved(posX, posY); } static void MouseScrollCallback(GLFWwindow* window, double xOffset, double yOffset) { GetWindow(window)->OnMouseScrolled(yOffset); } static void ResizeCallback(GLFWwindow* window, int width, int height) { GetWindow(window)->OnResize(width, height); } static void FocusCallback(GLFWwindow* window, const int focused) { if (focused == GLFW_TRUE) GetWindow(window)->OnFocusGained(); else GetWindow(window)->OnFocusLost(); } static void MinimizeCallback(GLFWwindow* window, const int minimized) { if(minimized == GLFW_TRUE) GetWindow(window)->OnMinimize(); else GetWindow(window)->OnRestore(); } static void RefreshCallback(GLFWwindow* window) { //TODO is there really anything to do? or is it ok if the window is only redrawn on the next frame? } static void WindowMoveCallback(GLFWwindow* window, const int posX, const int posY) { GetWindow(window)->OnMove(posX, posY); } static void CloseCallback(GLFWwindow* window) { GetWindow(window)->OnClose(); } static void DropCallback(GLFWwindow* window, const int count, const char** paths) { //TODO something useful } static void ErrorCallback(const int error, const char* description) { Logger::WINDOW->error("GLFW error (e{0}): {1}", error, description); } public: static std::vector GetVulkanRequiredInstanceExtensions() { std::vector result; uint32_t count = 0; const char** names = glfwGetRequiredInstanceExtensions(&count); if (names && count) { for (uint32_t i = 0; i < count; ++i) { result.emplace_back(names[i]); } } return result; } }; }