358 lines
11 KiB
C++
358 lines
11 KiB
C++
/*
|
||
* 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)
|
||
{
|
||
#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;
|
||
}
|
||
}
|