/* * Created by Brett on 28/11/23. * Licensed under GNU General Public License V3.0 * See LICENSE file for license detail */ #include #include #include #include #include #include #include #include "backends/imgui_impl_opengl3.h" #include "backends/imgui_impl_glfw.h" #include void error_callback(int error, const char* description) { BLT_ERROR("GLFW Error (%d): %s", error, description); std::abort(); } namespace blt::gfx { struct { /* GLFW Window Object */ GLFWwindow* window = nullptr; /* BLT internal input state manager, handles keyboard/mouse allocations + states */ input_manager inputManager; /* stores any drag and dropped paths for processing */ std::queue pendingPaths; /* current width and height of the window */ std::int32_t width = 0; std::int32_t height = 0; /* Frame time information (last frame) */ std::int64_t lastTime = blt::system::nanoTime(); std::int64_t deltaTime = 0; double nanoDelta = 0; double millisDelta = 0; } window_state; void create_callbacks() { /* Setup keyboard callback */ glfwSetKeyCallback(window_state.window, [](GLFWwindow*, int key, int, int action, int) { if (key < 0 || key == GLFW_KEY_UNKNOWN) return; KEY_STATE state; switch (action) { case GLFW_PRESS: state = KEY_STATE::PRESS; break; case GLFW_REPEAT: state = KEY_STATE::REPEAT; break; default: state = KEY_STATE::RELEASE; } window_state.inputManager.key(key) = state; window_state.inputManager.key_pressed = true; }); /* Setup mouse button callback */ glfwSetMouseButtonCallback(window_state.window, [](GLFWwindow*, int button, int action, int) { if (button < 0) return; MOUSE_STATE state; switch (action) { case GLFW_PRESS: state = MOUSE_STATE::PRESS; break; default: state = MOUSE_STATE::RELEASE; break; } window_state.inputManager.mouse(button) = state; window_state.inputManager.mouse_pressed = true; }); /* Setup mouse cursor callback */ glfwSetCursorPosCallback(window_state.window, [](GLFWwindow*, double x, double y) { window_state.inputManager.updateMousePos(x, y); window_state.inputManager.mouse_moved = true; }); /* Setup mouse scroll callback */ glfwSetScrollCallback(window_state.window, [](GLFWwindow*, double, double s) { window_state.inputManager.updateScroll(s); }); /* Setup drop input callback */ glfwSetDropCallback(window_state.window, [](GLFWwindow*, int count, const char** paths) { for (int i = 0; i < count; i++) window_state.pendingPaths.emplace(paths[i]); }); } void setup_ImGUI() { const char* glsl_version = "#version 100"; // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::Spectrum::StyleColorsSpectrum(); ImGui::Spectrum::LoadFont(); ImGui::SetupImGuiStyle(true, 1.0); // Setup FA ImFontConfig config; config.MergeMode = true; config.GlyphMinAdvanceX = 13.0f; // Use if you want to make the icon monospaced static const ImWchar icon_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0}; io.Fonts->AddFontFromMemoryCompressedBase85TTF(fontAwesomeRegular_compressed_data_base85, 13.0f, &config, icon_ranges); io.Fonts->AddFontFromMemoryCompressedTTF(fontAwesomeSolid_compressed_data, static_cast(fontAwesomeSolid_compressed_size), 13.0, &config, icon_ranges); io.Fonts->AddFontFromMemoryCompressedTTF(fontAwesomeBrands_compressed_data, static_cast(fontAwesomeBrands_compressed_size), 13.0, &config, icon_ranges); //ImGui::StyleColorsLight(); // Setup Platform/Renderer backends ImGui_ImplGlfw_InitForOpenGL(window_state.window, true); #ifdef __EMSCRIPTEN__ ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback("#canvas"); #endif ImGui_ImplOpenGL3_Init(glsl_version); #ifdef __EMSCRIPTEN__ io.IniFilename = nullptr; #endif } void loop(void* arg) { const auto& data = *((window_data*) arg); /* -- Get the current framebuffer size, update the global width/height state, along with OpenGL viewport -- */ glfwGetFramebufferSize(window_state.window, &window_state.width, &window_state.height); glViewport(0, 0, window_state.width, window_state.height); // TODO: user option for this glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* -- Begin the next ImGUI frame -- */ ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); /* -- Call user update function -- */ data.update(data.context, window_state.width, window_state.height); /* -- Render the ImGUI frame -- */ ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); /* -- Update GLFW state -- */ window_state.inputManager.clear(); glfwSwapBuffers(window_state.window); glfwPollEvents(); /* -- Update Frame Timing Information -- */ auto current_time = blt::system::nanoTime(); window_state.deltaTime = current_time - window_state.lastTime; window_state.lastTime = current_time; window_state.nanoDelta = static_cast(window_state.deltaTime) / 1e9f; window_state.millisDelta = static_cast(window_state.deltaTime) / 1e6f; } void init(const window_data& data) { #ifdef __EMSCRIPTEN__ blt::logging::setLogOutputFormat("[${{TIME}}] [${{LOG_LEVEL}}] (${{FILE}}:${{LINE}}) ${{STR}}\n"); #endif /* -- Set up Error Callback -- */ glfwSetErrorCallback(error_callback); BLT_ASSERT(glfwInit() && "Unable to init GLFW. Aborting."); /* -- Set up Window Context -- */ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, data.context.GL_MAJOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, data.context.GL_MINOR); glfwWindowHint(GLFW_DOUBLEBUFFER, data.context.DOUBLE_BUFFER); glfwWindowHint(GLFW_OPENGL_PROFILE, data.context.GL_PROFILE); glfwWindowHint(GLFW_SAMPLES, data.context.SAMPLES); /* -- Create the Window -- */ window_state.window = glfwCreateWindow(data.width, data.height, data.title.c_str(), nullptr, nullptr); BLT_ASSERT(window_state.window && "Unable to create GLFW window."); /* -- Set Window Specifics + OpenGL -- */ glfwMakeContextCurrent(window_state.window); #ifndef __EMSCRIPTEN__ glfwSwapInterval(data.sync_interval); gladLoadGL(glfwGetProcAddress); #endif /* -- Set up our local callbacks, ImGUI will then call these -- */ create_callbacks(); /* -- Set up ImGUI -- */ setup_ImGUI(); #ifdef GL_MULTISAMPLE if (data.context.SAMPLES > 0) glEnable(GL_MULTISAMPLE); #endif /* -- Call User Provided post-window-init function -- */ data.init(data.context); #ifdef __EMSCRIPTEN__ /* * "setting 0 or a negative value as the fps will use the browser’s requestAnimationFrame mechanism to call the main loop function. * This is HIGHLY recommended if you are doing rendering, as the browser’s requestAnimationFrame will * make sure you render at a proper smooth rate that lines up properly with the browser and monitor." * https://emscripten.org/docs/api_reference/emscripten.h.html */ emscripten_set_main_loop_arg(loop, (void*)&data, 0, true); #else /* -- General Loop -- */ while (!glfwWindowShouldClose(window_state.window)) loop((void*) &data); #endif } void cleanup() { ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwDestroyWindow(window_state.window); glfwTerminate(); } double getMouseX() { return window_state.inputManager.mouseX; } double getMouseY() { return window_state.inputManager.mouseY; } double getMouseDX() { return window_state.inputManager.deltaX; } double getMouseDY() { return window_state.inputManager.deltaY; } void lockCursor() { glfwSetInputMode(window_state.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); } void unlockCursor() { glfwSetInputMode(window_state.window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } bool isCursorLocked() { return glfwGetInputMode(window_state.window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED; } bool isCursorInWindow() { #ifdef __EMSCRIPTEN__ // TODO: return true; #else return glfwGetWindowAttrib(window_state.window, GLFW_HOVERED); #endif } void setRawInput(bool state) { #ifdef __EMSCRIPTEN__ // TODO? #else if (glfwRawMouseMotionSupported()) glfwSetInputMode(window_state.window, GLFW_RAW_MOUSE_MOTION, state ? GLFW_TRUE : GLFW_FALSE); #endif } bool isRawInput() { #ifdef __EMSCRIPTEN__ return false; #else return glfwGetInputMode(window_state.window, GLFW_RAW_MOUSE_MOTION); #endif } void setClipboard(const std::string& str) { glfwSetClipboardString(window_state.window, str.c_str()); } std::string getClipboard() { return glfwGetClipboardString(window_state.window); } bool isMousePressed(int button) { return window_state.inputManager.isMousePressed(button); } bool isKeyPressed(int key) { return window_state.inputManager.isKeyPressed(key); } double getFrameDeltaSeconds() { return window_state.nanoDelta; } double getFrameDeltaMilliseconds() { return window_state.millisDelta; } std::int64_t getFrameDelta() { return window_state.deltaTime; } bool mouseMovedLastFrame() { return window_state.inputManager.mouse_moved; } bool mousePressedLastFrame() { return window_state.inputManager.mouse_pressed; } bool keyPressedLastFrame() { return window_state.inputManager.key_pressed; } }