// 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. #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; } if (!gContext->isPlatformRendering) { PlatformRenderFrame(); } } static void FramebufferSizeCallback(GLFWwindow* window, int width, int height) { PlatformFramebufferSizeChanged(width, 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 ? 1 : 0, 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; } static void UpdateFontScale() { // Scale based on OS window content scaling float windowScale = 1.0; #ifndef __APPLE__ glfwGetWindowContentScale(gContext->window, &windowScale, nullptr); #endif // 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 int fontScale = gContext->userScale + static_cast((windowScale - 1.0) * 4); if (fontScale < 0) { fontScale = 0; } if (gContext->fontScale != fontScale) { gContext->reloadFonts = true; gContext->fontScale = fontScale; } } // the range is based on 13px being the "nominal" 100% size and going from // ~0.5x (7px) to ~2.0x (25px) static void ReloadFonts() { auto& io = ImGui::GetIO(); io.Fonts->Clear(); gContext->fonts.clear(); float size = 7.0f + gContext->fontScale * 3.0f; bool first = true; for (auto&& makeFont : gContext->makeFonts) { if (makeFont.second) { ImFontConfig cfg; std::snprintf(cfg.Name, sizeof(cfg.Name), "%s", makeFont.first); ImFont* font = makeFont.second(io, size, &cfg); if (first) { ImGui::GetIO().FontDefault = font; first = false; } gContext->fonts.emplace_back(font); } } } bool gui::Initialize(const char* title, int width, int height, ImGuiConfigFlags configFlags) { 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); glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE); PlatformGlfwInitHints(); if (!glfwInit()) { return false; } PlatformGlfwWindowHints(); // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImPlot::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= configFlags; // 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); if (gContext->loadSettings) { gContext->loadSettings(); io.IniFilename = nullptr; } else { io.IniFilename = gContext->iniPath.c_str(); } for (auto&& initialize : gContext->initializers) { if (initialize) { initialize(); } } // Load INI file if (gContext->loadIniSettings) { gContext->loadIniSettings(); } else if (io.IniFilename) { 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) { #ifndef __APPLE__ if (windowScale == 1.0) { glfwGetWindowContentScale(gContext->window, &windowScale, nullptr); } #endif // 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) { // 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); } glfwShowWindow(gContext->window); } // Set window callbacks glfwGetWindowSize(gContext->window, &gContext->width, &gContext->height); glfwSetWindowSizeCallback(gContext->window, WindowSizeCallback); glfwSetFramebufferSizeCallback(gContext->window, FramebufferSizeCallback); glfwSetWindowMaximizeCallback(gContext->window, WindowMaximizeCallback); glfwSetWindowPosCallback(gContext->window, WindowPosCallback); // Set icons if (!gContext->icons.empty()) { glfwSetWindowIcon(gContext->window, gContext->icons.size(), gContext->icons.data()); for (auto&& icon : gContext->icons) { stbi_image_free(icon.pixels); } gContext->icons.clear(); } // Setup Dear ImGui style SetStyle(static_cast