2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2020-08-25 23:52:09 -07:00
|
|
|
|
|
|
|
|
#include "wpigui.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
|
|
#include <GLFW/glfw3.h>
|
|
|
|
|
#include <imgui.h>
|
|
|
|
|
#include <imgui_ProggyDotted.h>
|
|
|
|
|
#include <imgui_impl_glfw.h>
|
|
|
|
|
#include <imgui_internal.h>
|
|
|
|
|
#include <implot.h>
|
2020-08-29 00:16:21 -07:00
|
|
|
#include <stb_image.h>
|
2021-06-27 01:48:54 -04:00
|
|
|
#include <wpi/fs.h>
|
2020-08-25 23:52:09 -07:00
|
|
|
|
|
|
|
|
#include "wpigui_internal.h"
|
|
|
|
|
|
|
|
|
|
using namespace wpi::gui;
|
|
|
|
|
|
|
|
|
|
namespace wpi {
|
|
|
|
|
|
|
|
|
|
Context* gui::gContext;
|
|
|
|
|
|
|
|
|
|
static void ErrorCallback(int error, const char* description) {
|
|
|
|
|
std::fprintf(stderr, "GLFW Error %d: %s\n", error, description);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void WindowSizeCallback(GLFWwindow* window, int width, int height) {
|
|
|
|
|
if (!gContext->maximized) {
|
|
|
|
|
gContext->width = width;
|
|
|
|
|
gContext->height = height;
|
|
|
|
|
}
|
2021-11-28 01:03:40 -08:00
|
|
|
if (!gContext->isPlatformRendering) {
|
|
|
|
|
PlatformRenderFrame();
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
2020-08-29 22:32:32 -07:00
|
|
|
static void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
|
|
|
|
|
PlatformFramebufferSizeChanged(width, height);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 23:52:09 -07:00
|
|
|
static void WindowMaximizeCallback(GLFWwindow* window, int maximized) {
|
|
|
|
|
gContext->maximized = maximized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void WindowPosCallback(GLFWwindow* window, int xpos, int ypos) {
|
|
|
|
|
if (!gContext->maximized) {
|
|
|
|
|
gContext->xPos = xpos;
|
|
|
|
|
gContext->yPos = ypos;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void* IniReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
|
|
|
|
const char* name) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (std::strcmp(name, "GLOBAL") != 0) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
return static_cast<SavedSettings*>(gContext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void IniReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
|
|
|
|
void* entry, const char* lineStr) {
|
|
|
|
|
auto impl = static_cast<SavedSettings*>(entry);
|
|
|
|
|
const char* value = std::strchr(lineStr, '=');
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!value) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
++value;
|
|
|
|
|
int num = std::atoi(value);
|
|
|
|
|
if (std::strncmp(lineStr, "width=", 6) == 0) {
|
|
|
|
|
impl->width = num;
|
|
|
|
|
impl->loadedWidthHeight = true;
|
|
|
|
|
} else if (std::strncmp(lineStr, "height=", 7) == 0) {
|
|
|
|
|
impl->height = num;
|
|
|
|
|
impl->loadedWidthHeight = true;
|
|
|
|
|
} else if (std::strncmp(lineStr, "maximized=", 10) == 0) {
|
|
|
|
|
impl->maximized = num;
|
|
|
|
|
} else if (std::strncmp(lineStr, "xpos=", 5) == 0) {
|
|
|
|
|
impl->xPos = num;
|
|
|
|
|
} else if (std::strncmp(lineStr, "ypos=", 5) == 0) {
|
|
|
|
|
impl->yPos = num;
|
|
|
|
|
} else if (std::strncmp(lineStr, "userScale=", 10) == 0) {
|
|
|
|
|
impl->userScale = num;
|
|
|
|
|
} else if (std::strncmp(lineStr, "style=", 6) == 0) {
|
|
|
|
|
impl->style = num;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void IniWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
|
|
|
|
ImGuiTextBuffer* out_buf) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!gContext) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
out_buf->appendf(
|
|
|
|
|
"[MainWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n"
|
|
|
|
|
"xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\n\n",
|
2021-11-25 00:52:08 -08:00
|
|
|
gContext->width, gContext->height, gContext->maximized ? 1 : 0,
|
|
|
|
|
gContext->xPos, gContext->yPos, gContext->userScale, gContext->style);
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::CreateContext() {
|
|
|
|
|
gContext = new Context;
|
|
|
|
|
AddFont("ProggyDotted", [](ImGuiIO& io, float size, const ImFontConfig* cfg) {
|
|
|
|
|
return ImGui::AddFontProggyDotted(io, size, cfg);
|
|
|
|
|
});
|
|
|
|
|
PlatformCreateContext();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::DestroyContext() {
|
|
|
|
|
PlatformDestroyContext();
|
|
|
|
|
delete gContext;
|
|
|
|
|
gContext = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool gui::Initialize(const char* title, int width, int height) {
|
|
|
|
|
gContext->title = title;
|
|
|
|
|
gContext->width = width;
|
|
|
|
|
gContext->height = height;
|
|
|
|
|
gContext->defaultWidth = width;
|
|
|
|
|
gContext->defaultHeight = height;
|
|
|
|
|
|
|
|
|
|
// Setup window
|
|
|
|
|
glfwSetErrorCallback(ErrorCallback);
|
|
|
|
|
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
|
|
|
|
|
PlatformGlfwInitHints();
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!glfwInit()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
|
|
|
|
|
PlatformGlfwWindowHints();
|
|
|
|
|
|
|
|
|
|
// Setup Dear ImGui context
|
|
|
|
|
IMGUI_CHECKVERSION();
|
|
|
|
|
ImGui::CreateContext();
|
|
|
|
|
ImPlot::CreateContext();
|
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
|
|
|
|
|
// Hook ini handler to save settings
|
|
|
|
|
ImGuiSettingsHandler iniHandler;
|
|
|
|
|
iniHandler.TypeName = "MainWindow";
|
|
|
|
|
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
|
|
|
|
|
iniHandler.ReadOpenFn = IniReadOpen;
|
|
|
|
|
iniHandler.ReadLineFn = IniReadLine;
|
|
|
|
|
iniHandler.WriteAllFn = IniWriteAll;
|
|
|
|
|
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
|
|
|
|
|
|
2021-11-07 23:21:18 -08:00
|
|
|
if (gContext->loadSettings) {
|
|
|
|
|
gContext->loadSettings();
|
|
|
|
|
io.IniFilename = nullptr;
|
|
|
|
|
} else {
|
|
|
|
|
io.IniFilename = gContext->iniPath.c_str();
|
|
|
|
|
}
|
2020-12-05 21:04:02 -05:00
|
|
|
|
2020-08-25 23:52:09 -07:00
|
|
|
for (auto&& initialize : gContext->initializers) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (initialize) {
|
|
|
|
|
initialize();
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load INI file
|
2021-11-07 23:21:18 -08:00
|
|
|
if (gContext->loadIniSettings) {
|
|
|
|
|
gContext->loadIniSettings();
|
|
|
|
|
} else if (io.IniFilename) {
|
|
|
|
|
ImGui::LoadIniSettingsFromDisk(io.IniFilename);
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
|
|
|
|
|
// Set initial window settings
|
|
|
|
|
glfwWindowHint(GLFW_MAXIMIZED, gContext->maximized ? GLFW_TRUE : GLFW_FALSE);
|
|
|
|
|
|
|
|
|
|
if (gContext->width == 0 || gContext->height == 0) {
|
|
|
|
|
gContext->width = gContext->defaultWidth;
|
|
|
|
|
gContext->height = gContext->defaultHeight;
|
|
|
|
|
gContext->loadedWidthHeight = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float windowScale = 1.0;
|
|
|
|
|
if (!gContext->loadedWidthHeight) {
|
|
|
|
|
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
|
|
|
|
|
// get the primary monitor work area to see if we have a reasonable initial
|
|
|
|
|
// window size; if not, maximize, and default scaling to smaller
|
|
|
|
|
if (GLFWmonitor* primary = glfwGetPrimaryMonitor()) {
|
|
|
|
|
int monWidth, monHeight;
|
|
|
|
|
glfwGetMonitorWorkarea(primary, nullptr, nullptr, &monWidth, &monHeight);
|
|
|
|
|
if (monWidth < gContext->width || monHeight < gContext->height) {
|
|
|
|
|
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
|
|
|
|
|
windowScale = (std::min)(monWidth * 1.0 / gContext->width,
|
|
|
|
|
monHeight * 1.0 / gContext->height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-28 12:58:06 -08:00
|
|
|
if (gContext->xPos != -1 && gContext->yPos != -1) {
|
2020-08-25 23:52:09 -07:00
|
|
|
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
|
|
|
|
|
// Create window with graphics context
|
|
|
|
|
gContext->window =
|
|
|
|
|
glfwCreateWindow(gContext->width, gContext->height,
|
|
|
|
|
gContext->title.c_str(), nullptr, nullptr);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!gContext->window) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
|
|
|
|
|
if (!gContext->loadedWidthHeight) {
|
2021-01-29 00:52:13 -05:00
|
|
|
#ifndef __APPLE__
|
2020-12-28 12:58:06 -08:00
|
|
|
if (windowScale == 1.0) {
|
2020-08-25 23:52:09 -07:00
|
|
|
glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2021-01-29 00:52:13 -05:00
|
|
|
#endif
|
2020-08-25 23:52:09 -07:00
|
|
|
// force user scale if window scale is smaller
|
2020-12-28 12:58:06 -08:00
|
|
|
if (windowScale <= 0.5) {
|
2020-08-25 23:52:09 -07:00
|
|
|
gContext->userScale = 0;
|
2020-12-28 12:58:06 -08:00
|
|
|
} else if (windowScale <= 0.75) {
|
2020-08-25 23:52:09 -07:00
|
|
|
gContext->userScale = 1;
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
if (windowScale != 1.0) {
|
2020-12-28 12:58:06 -08:00
|
|
|
for (auto&& func : gContext->windowScalers) {
|
|
|
|
|
func(windowScale);
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update window settings
|
|
|
|
|
if (gContext->xPos != -1 && gContext->yPos != -1) {
|
2021-03-21 12:39:33 -07:00
|
|
|
// check to make sure the position isn't off-screen
|
|
|
|
|
bool found = false;
|
|
|
|
|
int monCount;
|
|
|
|
|
GLFWmonitor** monitors = glfwGetMonitors(&monCount);
|
|
|
|
|
for (int i = 0; i < monCount; ++i) {
|
|
|
|
|
int monXPos, monYPos, monWidth, monHeight;
|
|
|
|
|
glfwGetMonitorWorkarea(monitors[i], &monXPos, &monYPos, &monWidth,
|
|
|
|
|
&monHeight);
|
|
|
|
|
if (gContext->xPos >= monXPos && gContext->xPos < (monXPos + monWidth) &&
|
|
|
|
|
gContext->yPos >= monYPos && gContext->yPos < (monYPos + monHeight)) {
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (found) {
|
|
|
|
|
glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos);
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
glfwShowWindow(gContext->window);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set window callbacks
|
|
|
|
|
glfwGetWindowSize(gContext->window, &gContext->width, &gContext->height);
|
|
|
|
|
glfwSetWindowSizeCallback(gContext->window, WindowSizeCallback);
|
2020-08-29 22:32:32 -07:00
|
|
|
glfwSetFramebufferSizeCallback(gContext->window, FramebufferSizeCallback);
|
2020-08-25 23:52:09 -07:00
|
|
|
glfwSetWindowMaximizeCallback(gContext->window, WindowMaximizeCallback);
|
|
|
|
|
glfwSetWindowPosCallback(gContext->window, WindowPosCallback);
|
|
|
|
|
|
2020-12-23 13:05:25 -08:00
|
|
|
// Set icons
|
|
|
|
|
if (!gContext->icons.empty()) {
|
|
|
|
|
glfwSetWindowIcon(gContext->window, gContext->icons.size(),
|
|
|
|
|
gContext->icons.data());
|
2020-12-28 12:58:06 -08:00
|
|
|
for (auto&& icon : gContext->icons) {
|
|
|
|
|
stbi_image_free(icon.pixels);
|
|
|
|
|
}
|
2020-12-23 13:05:25 -08:00
|
|
|
gContext->icons.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 23:52:09 -07:00
|
|
|
// Setup Dear ImGui style
|
|
|
|
|
SetStyle(static_cast<Style>(gContext->style));
|
|
|
|
|
|
|
|
|
|
// Load Fonts
|
|
|
|
|
// this range is based on 13px being the "nominal" 100% size and going from
|
|
|
|
|
// ~0.5x (7px) to ~2.0x (25px)
|
|
|
|
|
for (auto&& makeFont : gContext->makeFonts) {
|
|
|
|
|
if (makeFont.second) {
|
|
|
|
|
auto& font = gContext->fonts.emplace_back();
|
|
|
|
|
for (int i = 0; i < Font::kScaledLevels; ++i) {
|
|
|
|
|
float size = 7.0f + i * 3.0f;
|
|
|
|
|
ImFontConfig cfg;
|
|
|
|
|
std::snprintf(cfg.Name, sizeof(cfg.Name), "%s-%d", makeFont.first,
|
|
|
|
|
static_cast<int>(size));
|
|
|
|
|
font.scaled[i] = makeFont.second(io, size, &cfg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!PlatformInitRenderer()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::Main() {
|
|
|
|
|
// Main loop
|
|
|
|
|
while (!glfwWindowShouldClose(gContext->window) && !gContext->exit) {
|
|
|
|
|
// Poll and handle events (inputs, window resize, etc.)
|
|
|
|
|
glfwPollEvents();
|
2021-11-28 01:03:40 -08:00
|
|
|
gContext->isPlatformRendering = true;
|
2020-08-25 23:52:09 -07:00
|
|
|
PlatformRenderFrame();
|
2021-11-28 01:03:40 -08:00
|
|
|
gContext->isPlatformRendering = false;
|
2021-11-07 23:21:18 -08:00
|
|
|
|
|
|
|
|
// custom saving
|
|
|
|
|
if (gContext->saveSettings) {
|
|
|
|
|
auto& io = ImGui::GetIO();
|
|
|
|
|
if (io.WantSaveIniSettings) {
|
|
|
|
|
gContext->saveSettings(false);
|
|
|
|
|
io.WantSaveIniSettings = false; // reset flag
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save (if custom save)
|
|
|
|
|
if (gContext->saveSettings) {
|
|
|
|
|
gContext->saveSettings(true);
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
|
PlatformShutdown();
|
|
|
|
|
ImGui_ImplGlfw_Shutdown();
|
|
|
|
|
ImPlot::DestroyContext();
|
|
|
|
|
ImGui::DestroyContext();
|
|
|
|
|
|
2021-06-27 01:48:54 -04:00
|
|
|
// Delete the save file if requested.
|
2021-11-07 23:21:18 -08:00
|
|
|
if (!gContext->saveSettings && gContext->resetOnExit) {
|
2021-06-27 01:48:54 -04:00
|
|
|
fs::remove(fs::path{gContext->iniPath});
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 23:52:09 -07:00
|
|
|
glfwDestroyWindow(gContext->window);
|
|
|
|
|
glfwTerminate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::CommonRenderFrame() {
|
|
|
|
|
ImGui_ImplGlfw_NewFrame();
|
|
|
|
|
|
|
|
|
|
// Start the Dear ImGui frame
|
|
|
|
|
ImGui::NewFrame();
|
|
|
|
|
|
|
|
|
|
// Scale based on OS window content scaling
|
|
|
|
|
float windowScale = 1.0;
|
2021-01-29 00:52:13 -05:00
|
|
|
#ifndef __APPLE__
|
2020-08-25 23:52:09 -07:00
|
|
|
glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
|
2021-01-29 00:52:13 -05:00
|
|
|
#endif
|
2020-08-25 23:52:09 -07:00
|
|
|
// map to closest font size: 0 = 0.5x, 1 = 0.75x, 2 = 1.0x, 3 = 1.25x,
|
|
|
|
|
// 4 = 1.5x, 5 = 1.75x, 6 = 2x
|
|
|
|
|
gContext->fontScale = std::clamp(
|
|
|
|
|
gContext->userScale + static_cast<int>((windowScale - 1.0) * 4), 0,
|
|
|
|
|
Font::kScaledLevels - 1);
|
|
|
|
|
ImGui::GetIO().FontDefault = gContext->fonts[0].scaled[gContext->fontScale];
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < gContext->earlyExecutors.size(); ++i) {
|
|
|
|
|
auto& execute = gContext->earlyExecutors[i];
|
2020-12-28 12:58:06 -08:00
|
|
|
if (execute) {
|
|
|
|
|
execute();
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < gContext->lateExecutors.size(); ++i) {
|
|
|
|
|
auto& execute = gContext->lateExecutors[i];
|
2020-12-28 12:58:06 -08:00
|
|
|
if (execute) {
|
|
|
|
|
execute();
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rendering
|
|
|
|
|
ImGui::Render();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-29 00:16:21 -07:00
|
|
|
void gui::Exit() {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!gContext) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-08-29 00:16:21 -07:00
|
|
|
gContext->exit = true;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
|
|
|
|
|
void gui::AddInit(std::function<void()> initialize) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (initialize) {
|
|
|
|
|
gContext->initializers.emplace_back(std::move(initialize));
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::AddWindowScaler(std::function<void(float scale)> windowScaler) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (windowScaler) {
|
2020-08-25 23:52:09 -07:00
|
|
|
gContext->windowScalers.emplace_back(std::move(windowScaler));
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::AddEarlyExecute(std::function<void()> execute) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (execute) {
|
|
|
|
|
gContext->earlyExecutors.emplace_back(std::move(execute));
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::AddLateExecute(std::function<void()> execute) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (execute) {
|
|
|
|
|
gContext->lateExecutors.emplace_back(std::move(execute));
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
|
2021-11-07 23:21:18 -08:00
|
|
|
void gui::ConfigureCustomSaveSettings(std::function<void()> load,
|
|
|
|
|
std::function<void()> loadIni,
|
|
|
|
|
std::function<void(bool)> save) {
|
|
|
|
|
gContext->loadSettings = load;
|
|
|
|
|
gContext->loadIniSettings = loadIni;
|
|
|
|
|
gContext->saveSettings = save;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-28 12:58:06 -08:00
|
|
|
GLFWwindow* gui::GetSystemWindow() {
|
|
|
|
|
return gContext->window;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
|
2020-12-23 13:05:25 -08:00
|
|
|
bool gui::AddIcon(const unsigned char* data, int len) {
|
|
|
|
|
// Load from memory
|
|
|
|
|
GLFWimage image;
|
|
|
|
|
image.pixels =
|
|
|
|
|
stbi_load_from_memory(data, len, &image.width, &image.height, nullptr, 4);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!data) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-12-23 13:05:25 -08:00
|
|
|
gContext->icons.emplace_back(std::move(image));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 23:52:09 -07:00
|
|
|
int gui::AddFont(
|
|
|
|
|
const char* name,
|
|
|
|
|
std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>
|
|
|
|
|
makeFont) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (makeFont) {
|
|
|
|
|
gContext->makeFonts.emplace_back(name, std::move(makeFont));
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
return gContext->makeFonts.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImFont* gui::GetFont(int font) {
|
|
|
|
|
return gContext->fonts[font].scaled[gContext->fontScale];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::SetStyle(Style style) {
|
|
|
|
|
gContext->style = static_cast<int>(style);
|
|
|
|
|
switch (style) {
|
|
|
|
|
case kStyleClassic:
|
|
|
|
|
ImGui::StyleColorsClassic();
|
|
|
|
|
break;
|
|
|
|
|
case kStyleDark:
|
|
|
|
|
ImGui::StyleColorsDark();
|
|
|
|
|
break;
|
|
|
|
|
case kStyleLight:
|
|
|
|
|
ImGui::StyleColorsLight();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-28 12:58:06 -08:00
|
|
|
void gui::SetClearColor(ImVec4 color) {
|
|
|
|
|
gContext->clearColor = color;
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
|
2021-11-07 23:21:18 -08:00
|
|
|
std::string gui::GetPlatformSaveFileDir() {
|
2020-12-05 21:04:02 -05:00
|
|
|
#if defined(_MSC_VER)
|
|
|
|
|
const char* env = std::getenv("APPDATA");
|
2021-01-25 10:40:12 -05:00
|
|
|
if (env) {
|
2021-11-07 23:21:18 -08:00
|
|
|
return env + std::string("/");
|
2021-01-25 10:40:12 -05:00
|
|
|
}
|
2020-12-05 21:04:02 -05:00
|
|
|
#elif defined(__APPLE__)
|
|
|
|
|
const char* env = std::getenv("HOME");
|
2021-01-25 10:40:12 -05:00
|
|
|
if (env) {
|
2021-11-07 23:21:18 -08:00
|
|
|
return env + std::string("/Library/Preferences/");
|
2021-01-25 10:40:12 -05:00
|
|
|
}
|
2020-12-05 21:04:02 -05:00
|
|
|
#else
|
|
|
|
|
const char* xdg = std::getenv("XDG_CONFIG_HOME");
|
|
|
|
|
const char* env = std::getenv("HOME");
|
2020-12-28 12:58:06 -08:00
|
|
|
if (xdg) {
|
2021-11-07 23:21:18 -08:00
|
|
|
return xdg + std::string("/");
|
2020-12-28 12:58:06 -08:00
|
|
|
} else if (env) {
|
2021-11-07 23:21:18 -08:00
|
|
|
return env + std::string("/.config/");
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-12-05 21:04:02 -05:00
|
|
|
#endif
|
2021-11-07 23:21:18 -08:00
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui::ConfigurePlatformSaveFile(const std::string& name) {
|
|
|
|
|
gContext->iniPath = GetPlatformSaveFileDir() + name;
|
2020-12-05 21:04:02 -05:00
|
|
|
}
|
|
|
|
|
|
2020-08-25 23:52:09 -07:00
|
|
|
void gui::EmitViewMenu() {
|
|
|
|
|
if (ImGui::BeginMenu("View")) {
|
|
|
|
|
if (ImGui::BeginMenu("Style")) {
|
|
|
|
|
bool selected;
|
|
|
|
|
selected = gContext->style == kStyleClassic;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (ImGui::MenuItem("Classic", nullptr, &selected, true)) {
|
2020-08-25 23:52:09 -07:00
|
|
|
SetStyle(kStyleClassic);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
selected = gContext->style == kStyleDark;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (ImGui::MenuItem("Dark", nullptr, &selected, true)) {
|
2020-08-25 23:52:09 -07:00
|
|
|
SetStyle(kStyleDark);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
selected = gContext->style == kStyleLight;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (ImGui::MenuItem("Light", nullptr, &selected, true)) {
|
2020-08-25 23:52:09 -07:00
|
|
|
SetStyle(kStyleLight);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
ImGui::EndMenu();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ImGui::BeginMenu("Zoom")) {
|
|
|
|
|
for (int i = 0; i < Font::kScaledLevels && (25 * (i + 2)) <= 200; ++i) {
|
|
|
|
|
char label[20];
|
|
|
|
|
std::snprintf(label, sizeof(label), "%d%%", 25 * (i + 2));
|
|
|
|
|
bool selected = gContext->userScale == i;
|
|
|
|
|
bool enabled = (gContext->fontScale - gContext->userScale + i) >= 0 &&
|
|
|
|
|
(gContext->fontScale - gContext->userScale + i) <
|
|
|
|
|
Font::kScaledLevels;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (ImGui::MenuItem(label, nullptr, &selected, enabled)) {
|
2020-08-25 23:52:09 -07:00
|
|
|
gContext->userScale = i;
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
}
|
|
|
|
|
ImGui::EndMenu();
|
|
|
|
|
}
|
2021-06-27 01:48:54 -04:00
|
|
|
|
2021-11-07 23:21:18 -08:00
|
|
|
if (!gContext->saveSettings) {
|
|
|
|
|
ImGui::MenuItem("Reset UI on Exit?", nullptr, &gContext->resetOnExit);
|
|
|
|
|
}
|
2020-08-25 23:52:09 -07:00
|
|
|
ImGui::EndMenu();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-29 00:16:21 -07:00
|
|
|
bool gui::UpdateTextureFromImage(ImTextureID* texture, int width, int height,
|
|
|
|
|
const unsigned char* data, int len) {
|
|
|
|
|
// Load from memory
|
|
|
|
|
int width2 = 0;
|
|
|
|
|
int height2 = 0;
|
|
|
|
|
unsigned char* imgData =
|
|
|
|
|
stbi_load_from_memory(data, len, &width2, &height2, nullptr, 4);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!data) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-08-29 00:16:21 -07:00
|
|
|
|
2020-12-28 12:58:06 -08:00
|
|
|
if (width2 == width && height2 == height) {
|
2020-08-29 00:16:21 -07:00
|
|
|
UpdateTexture(texture, kPixelRGBA, width2, height2, imgData);
|
2020-12-28 12:58:06 -08:00
|
|
|
} else {
|
2020-08-29 00:16:21 -07:00
|
|
|
*texture = CreateTexture(kPixelRGBA, width2, height2, imgData);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-08-29 00:16:21 -07:00
|
|
|
|
|
|
|
|
stbi_image_free(imgData);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool gui::CreateTextureFromFile(const char* filename, ImTextureID* out_texture,
|
|
|
|
|
int* out_width, int* out_height) {
|
|
|
|
|
// Load from file
|
|
|
|
|
int width = 0;
|
|
|
|
|
int height = 0;
|
|
|
|
|
unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!data) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-08-29 00:16:21 -07:00
|
|
|
|
|
|
|
|
*out_texture = CreateTexture(kPixelRGBA, width, height, data);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (out_width) {
|
|
|
|
|
*out_width = width;
|
|
|
|
|
}
|
|
|
|
|
if (out_height) {
|
|
|
|
|
*out_height = height;
|
|
|
|
|
}
|
2020-08-29 00:16:21 -07:00
|
|
|
|
|
|
|
|
stbi_image_free(data);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool gui::CreateTextureFromImage(const unsigned char* data, int len,
|
|
|
|
|
ImTextureID* out_texture, int* out_width,
|
|
|
|
|
int* out_height) {
|
|
|
|
|
// Load from memory
|
|
|
|
|
int width = 0;
|
|
|
|
|
int height = 0;
|
|
|
|
|
unsigned char* imgData =
|
|
|
|
|
stbi_load_from_memory(data, len, &width, &height, nullptr, 4);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!imgData) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-08-29 00:16:21 -07:00
|
|
|
|
|
|
|
|
*out_texture = CreateTexture(kPixelRGBA, width, height, imgData);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (out_width) {
|
|
|
|
|
*out_width = width;
|
|
|
|
|
}
|
|
|
|
|
if (out_height) {
|
|
|
|
|
*out_height = height;
|
|
|
|
|
}
|
2020-08-29 00:16:21 -07:00
|
|
|
|
|
|
|
|
stbi_image_free(imgData);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-29 10:32:49 -07:00
|
|
|
void gui::MaxFit(ImVec2* min, ImVec2* max, float width, float height) {
|
|
|
|
|
float destWidth = max->x - min->x;
|
|
|
|
|
float destHeight = max->y - min->y;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (width == 0 || height == 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-08-29 10:32:49 -07:00
|
|
|
if (destWidth * height > destHeight * width) {
|
|
|
|
|
float outputWidth = width * destHeight / height;
|
|
|
|
|
min->x += (destWidth - outputWidth) / 2;
|
|
|
|
|
max->x -= (destWidth - outputWidth) / 2;
|
|
|
|
|
} else {
|
|
|
|
|
float outputHeight = height * destWidth / width;
|
|
|
|
|
min->y += (destHeight - outputHeight) / 2;
|
|
|
|
|
max->y -= (destHeight - outputHeight) / 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 23:52:09 -07:00
|
|
|
} // namespace wpi
|