/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include "WindowGLFW.hpp" #include "Base/Logger.hpp" #include namespace OpenVulkano::GLFW { WindowGLFW::WindowGLFW(OpenVulkano::GLFW::InputProviderGLFW& inputProvider) : inputProvider(inputProvider) {} WindowGLFW::~WindowGLFW() noexcept { if (window) { inputProvider.Close(); Close(); } } GLFWmonitor* WindowGLFW::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.position.x >= posX && windowConfig.position.x < posX + sizeX && windowConfig.position.y >= posY && windowConfig.position.y < posY + sizeY) { return monitors[i]; } } return nullptr; } GLFWmonitor* WindowGLFW::GetTargetMonitor() const { if (windowConfig.windowMode == FULLSCREEN) { // TODO add config to control used display return GetPrimaryMonitor(); } return nullptr; } void WindowGLFW::Create() { glfwWindowHint(GLFW_DECORATED, (~windowConfig.windowMode) & 1); //TODO handle full screen resolutions window = glfwCreateWindow(windowConfig.size.x, windowConfig.size.y, windowConfig.title.c_str(), GetTargetMonitor(), nullptr); if (!window) return; glfwSetWindowUserPointer(window, this); RegisterCallbacks(); } void WindowGLFW::RegisterCallbacks() const { 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); glfwSetCursorEnterCallback(window, MouseEnterExitCallback); // Set mouse visibility } void WindowGLFW::SetMouseVisibility(bool hideMouse) { glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, hideMouse); glfwSetInputMode(window, GLFW_CURSOR, hideMouse ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); } GLFWmonitor* WindowGLFW::GetPrimaryMonitor() { return glfwGetPrimaryMonitor(); } std::vector WindowGLFW::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; } void WindowGLFW::Init(RenderAPI::RenderApi renderApi) { if (renderApi == RenderAPI::Vulkan) glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); Create(); if (!window) { throw WindowInitFailedException("Failed to initialize window"); } if (renderApi != RenderAPI::Vulkan) MakeCurrentThread(); Logger::WINDOW->info("GLFW Window created (id: {0})", GetWindowId()); } void WindowGLFW::Close() { glfwDestroyWindow(window); window = nullptr; Logger::WINDOW->info("GLFW Window destroyed (id: {0})", GetWindowId()); } void WindowGLFW::Present() const { glfwSwapBuffers(window); } void WindowGLFW::Show() { glfwShowWindow(window); } void WindowGLFW::Hide() { glfwHideWindow(window); } void WindowGLFW::SetTitle(const std::string& title) { windowConfig.title = title; if (window) { glfwSetWindowTitle(window, title.c_str()); } } void WindowGLFW::SetSize(uint32_t width, uint32_t height) { currentSize = { width, height }; if (window) { glfwSetWindowSize(window, width, height); } } void WindowGLFW::SetPosition(int posX, int posY) { windowConfig.position.x = posX; windowConfig.position.y = posY; if (window) { glfwSetWindowPos(window, posX, posY); } } Math::Vector2ui WindowGLFW::GetSize() { if (currentSize.x == 0 || currentSize.y == 0) glfwGetWindowSize(window, reinterpret_cast(¤tSize.x), reinterpret_cast(¤tSize.y)); return currentSize; } Math::Vector2i WindowGLFW::GetPosition() { Math::Vector2i pos; glfwGetWindowPos(window, &pos.x, &pos.y); return pos; } void WindowGLFW::SetSizeLimits(int minWidth, int minHeight, int maxWidth, int maxHeight) { 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 WindowGLFW::MakeCurrentThread() { glfwMakeContextCurrent(window); } void WindowGLFW::SetWindowMode(WindowMode windowMode) { 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.size.x; sizeY = windowConfig.size.y; posX = windowConfig.position.x; posY = windowConfig.position.y; monitor = nullptr; if (windowMode == WINDOWED) { Logger::WINDOW->info("Switching to Windowed mode"); } else { Logger::WINDOW->info("Switching to Borderless Windowed mode"); } } else { // Fullscreen windowConfig.position = GetPosition(); // 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 WindowGLFW::SetWindowHandler(IWindowHandler* handler) { this->handler = handler; } vk::SurfaceKHR WindowGLFW::CreateSurface(const vk::Instance& instance, const vk::AllocationCallbacks* pAllocator) { VkSurfaceKHR rawSurface; const vk::Result result = static_cast(glfwCreateWindowSurface(static_cast(instance), window, reinterpret_cast(pAllocator), &rawSurface)); if (result != vk::Result::eSuccess) [[unlikely]] { Logger::WINDOW->error("Failed to create vulkan surface. Result: {}", vk::to_string(result)); } return rawSurface;//createResultValue(result, rawSurface, "vk::CommandBuffer::begin"); } std::vector WindowGLFW::GetRequiredInstanceExtensions() { return GetVulkanRequiredInstanceExtensions(); } void WindowGLFW::OnResize(const uint32_t newWidth, const uint32_t newHeight) { Logger::WINDOW->debug("Window (id: {0}) resized (width: {1}, height: {2})", GetWindowId(), newWidth, newHeight); currentSize = { newWidth, newHeight }; if (GetWindowMode() == WINDOWED || GetWindowMode() == BORDERLESS) { windowConfig.size = currentSize; } handler->OnWindowResize(this, newWidth, newHeight); } void WindowGLFW::OnMinimize() { Logger::WINDOW->debug("Window (id: {0}) minimized", GetWindowId()); handler->OnWindowMinimize(this); } void WindowGLFW::OnRestore() { Logger::WINDOW->debug("Window (id: {0}) restored", GetWindowId()); handler->OnWindowRestore(this); } void WindowGLFW::OnFocusLost() { Logger::WINDOW->trace("Window (id: {0}) focus lost", GetWindowId()); handler->OnWindowFocusLost(this); } void WindowGLFW::OnFocusGained() { Logger::WINDOW->trace("Window (id: {0}) focus gained", GetWindowId()); handler->OnWindowFocusGained(this); } void WindowGLFW::OnMove(const int posX, const int posY) { Logger::WINDOW->trace("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.position.x = posX; windowConfig.position.y = posY; } handler->OnWindowMove(this, posX, posY); } void WindowGLFW::OnClose() { Logger::WINDOW->debug("Window (id: {0}) closed", GetWindowId()); handler->OnWindowClose(this); } void WindowGLFW::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); Logger::INPUT->debug("Key Pressed: {0} Mod: {1} ScanCode: {2}", key, mods, scanCode); break; case GLFW_RELEASE:// OnKeyReleased(key, mods); Logger::INPUT->debug("Key Released: {0} Mod: {1} ScanCode: {2}", key, mods, scanCode); break; case GLFW_REPEAT: Logger::INPUT->debug("Key Repeat: {0} Mod: {1} ScanCode: {2}", key, mods, scanCode); break; default: break; } } WindowGLFW* WindowGLFW::GetWindow(GLFWwindow* window) { return static_cast(glfwGetWindowUserPointer(window)); } void WindowGLFW::KeyboardCallback(GLFWwindow* window, int key, int scanCode, int action, int mods) { GetWindow(window)->OnKeyEvent(key, scanCode, action, mods); const auto windowInstance = GetWindow(window); windowInstance->inputProvider.SetKeyboardKey(windowInstance, key, scanCode, action, mods); } void WindowGLFW::MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { GetWindow(window)->inputProvider.SetMouseButton(button, action != GLFW_RELEASE); } void WindowGLFW::MouseMoveCallback(GLFWwindow* window, double posX, double posY) { GetWindow(window)->inputProvider.mouse.UpdatePosition(posX, posY); } void WindowGLFW::MouseScrollCallback(GLFWwindow* window, double xOffset, double yOffset) { GetWindow(window)->inputProvider.mouse.UpdateWheel(xOffset, yOffset); } void WindowGLFW::MouseEnterExitCallback(GLFWwindow* window, int entered) { const auto windowInstance = GetWindow(window); windowInstance->inputProvider.MouseEnterExitWindow(windowInstance); Logger::INPUT->trace("Mouse enter/exit: {}", entered); } void WindowGLFW::ResizeCallback(GLFWwindow* window, int width, int height) { GetWindow(window)->OnResize(width, height); } void WindowGLFW::FocusCallback(GLFWwindow* window, const int focused) { if (focused == GLFW_TRUE) GetWindow(window)->OnFocusGained(); else GetWindow(window)->OnFocusLost(); } void WindowGLFW::MinimizeCallback(GLFWwindow* window, const int minimized) { if (minimized == GLFW_TRUE) GetWindow(window)->OnMinimize(); else GetWindow(window)->OnRestore(); } void WindowGLFW::RefreshCallback(GLFWwindow* window) { //TODO is there really anything to do? or is it ok if the window is only redrawn on the next frame? } void WindowGLFW::WindowMoveCallback(GLFWwindow* window, const int posX, const int posY) { GetWindow(window)->OnMove(posX, posY); } void WindowGLFW::CloseCallback(GLFWwindow* window) { GetWindow(window)->OnClose(); } void WindowGLFW::DropCallback(GLFWwindow* window, const int count, const char** paths) { //TODO something useful } std::vector WindowGLFW::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; } }