/*----------------------------------------------------------------------------*/ /* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ /*----------------------------------------------------------------------------*/ #include "wpigui.h" #include #include #include #include #include #include #include #include #include #include #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; } } 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) { if (std::strcmp(name, "GLOBAL") != 0) return nullptr; return static_cast(gContext); } static void IniReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* lineStr) { auto impl = static_cast(entry); const char* value = std::strchr(lineStr, '='); if (!value) return; ++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) { if (!gContext) return; out_buf->appendf( "[MainWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n" "xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\n\n", gContext->width, gContext->height, gContext->maximized, gContext->xPos, gContext->yPos, gContext->userScale, gContext->style); } 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(); if (!glfwInit()) return false; 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); for (auto&& initialize : gContext->initializers) { if (initialize) initialize(); } // Load INI file ImGui::LoadIniSettingsFromDisk(io.IniFilename); // 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); } } } if (gContext->xPos != -1 && gContext->yPos != -1) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Create window with graphics context gContext->window = glfwCreateWindow(gContext->width, gContext->height, gContext->title.c_str(), nullptr, nullptr); if (!gContext->window) return false; if (!gContext->loadedWidthHeight) { if (windowScale == 1.0) glfwGetWindowContentScale(gContext->window, &windowScale, nullptr); // force user scale if window scale is smaller if (windowScale <= 0.5) gContext->userScale = 0; else if (windowScale <= 0.75) gContext->userScale = 1; if (windowScale != 1.0) { for (auto&& func : gContext->windowScalers) func(windowScale); } } // Update window settings if (gContext->xPos != -1 && gContext->yPos != -1) { glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos); glfwShowWindow(gContext->window); } // Set window callbacks glfwGetWindowSize(gContext->window, &gContext->width, &gContext->height); glfwSetWindowSizeCallback(gContext->window, WindowSizeCallback); glfwSetWindowMaximizeCallback(gContext->window, WindowMaximizeCallback); glfwSetWindowPosCallback(gContext->window, WindowPosCallback); // Setup Dear ImGui style SetStyle(static_cast