BLT-With-Graphics-Template/src/blt/gfx/window.cpp

353 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Created by Brett on 28/11/23.
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
#include <blt/gfx/window.h>
#include <blt/std/assert.h>
#include <blt/std/logging.h>
#include <blt/std/time.h>
#include <blt/gfx/input.h>
#include <queue>
#include <imgui.h>
#include "backends/imgui_impl_opengl3.h"
#include "backends/imgui_impl_glfw.h"
#include <blt/gfx/imgui/ImGuiUtils.h>
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<std::string> 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<int>(fontAwesomeSolid_compressed_size), 13.0, &config,
icon_ranges);
io.Fonts->AddFontFromMemoryCompressedTTF(fontAwesomeBrands_compressed_data, static_cast<int>(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<double>(window_state.deltaTime) / 1e9f;
window_state.millisDelta = static_cast<double>(window_state.deltaTime) / 1e6f;
}
void init(const window_data& data)
{
/* -- 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);
glfwSwapInterval(data.sync_interval);
#ifndef __EMSCRIPTEN__
gladLoadGL(glfwGetProcAddress);
#endif
/* -- Set up our local callbacks, ImGUI will then call these -- */
create_callbacks();
/* -- Set up ImGUI -- */
setup_ImGUI();
if (data.context.SAMPLES > 0)
glEnable(GL_MULTISAMPLE);
/* -- 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 browsers requestAnimationFrame mechanism to call the main loop function.
* This is HIGHLY recommended if you are doing rendering, as the browsers 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;
}
}