diff --git a/.styleguide b/.styleguide index 498109678d..bec7a2f60f 100644 --- a/.styleguide +++ b/.styleguide @@ -36,5 +36,6 @@ includeOtherLibs { ^unsupported/ ^vision/ ^wpi/ + ^wpigui ^wpimath/ } diff --git a/simulation/halsim_gui/CMakeLists.txt b/simulation/halsim_gui/CMakeLists.txt index 6a7b2e368b..5d821269b2 100644 --- a/simulation/halsim_gui/CMakeLists.txt +++ b/simulation/halsim_gui/CMakeLists.txt @@ -7,7 +7,7 @@ file(GLOB halsim_gui_src src/main/native/cpp/*.cpp) add_library(halsim_gui MODULE ${halsim_gui_src}) wpilib_target_warnings(halsim_gui) set_target_properties(halsim_gui PROPERTIES DEBUG_POSTFIX "d") -target_link_libraries(halsim_gui PUBLIC hal ntcore wpimath PRIVATE imgui) +target_link_libraries(halsim_gui PUBLIC hal ntcore wpimath PRIVATE wpigui) target_include_directories(halsim_gui PRIVATE src/main/native/include) diff --git a/simulation/halsim_gui/build.gradle b/simulation/halsim_gui/build.gradle index 7b9850bdea..0fd5c2c76b 100644 --- a/simulation/halsim_gui/build.gradle +++ b/simulation/halsim_gui/build.gradle @@ -24,15 +24,16 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxra binaries { all { lib project: ':wpimath', library: 'wpimath', linkage: 'shared' + lib project: ':wpigui', library: 'wpigui', linkage: 'static' nativeUtils.useRequiredLibrary(it, 'imgui_static') if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) { it.buildable = false return } if (it.targetPlatform.operatingSystem.isWindows()) { - it.linker.args << 'Gdi32.lib' << 'Shell32.lib' + it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib' } else if (it.targetPlatform.operatingSystem.isMacOsX()) { - it.linker.args << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' + it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore' } else { it.linker.args << '-lX11' } diff --git a/simulation/halsim_gui/src/main/native/cpp/Field2D.cpp b/simulation/halsim_gui/src/main/native/cpp/Field2D.cpp index c5089b4b0e..ea3f7fd491 100644 --- a/simulation/halsim_gui/src/main/native/cpp/Field2D.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/Field2D.cpp @@ -9,12 +9,11 @@ #include -#include #include -#include #define IMGUI_DEFINE_MATH_OPERATORS #include +#include #include #include #include @@ -23,6 +22,7 @@ #include #include #include +#include #include "GuiUtil.h" #include "HALSimGui.h" @@ -31,6 +31,8 @@ using namespace halsimgui; +namespace gui = wpi::gui; + namespace { // Per-frame field data (not persistent) @@ -64,10 +66,10 @@ class FieldInfo { void WriteIni(ImGuiTextBuffer* out) const; private: - bool LoadImageImpl(const wpi::Twine& fn); + bool LoadImageImpl(const char* fn); std::string m_filename; - GLuint m_texture = 0; + ImTextureID m_texture = 0; int m_imageWidth = 0; int m_imageHeight = 0; int m_top = 0; @@ -114,10 +116,10 @@ class RobotInfo { void WriteIni(ImGuiTextBuffer* out) const; private: - bool LoadImageImpl(const wpi::Twine& fn); + bool LoadImageImpl(const char* fn); std::string m_filename; - GLuint m_texture = 0; + ImTextureID m_texture = 0; HAL_SimDeviceHandle m_devHandle = 0; hal::SimDouble m_xHandle; @@ -164,7 +166,7 @@ static void Field2DWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, } void FieldInfo::Reset() { - if (m_texture != 0) glDeleteTextures(1, &m_texture); + if (m_texture != 0) gui::DeleteTexture(m_texture); m_texture = 0; m_filename.clear(); m_imageWidth = 0; @@ -182,7 +184,7 @@ void FieldInfo::LoadImage() { if (wpi::StringRef(result[0]).endswith(".json")) { LoadJson(result[0]); } else { - LoadImageImpl(result[0]); + LoadImageImpl(result[0].c_str()); m_top = 0; m_left = 0; m_bottom = -1; @@ -192,7 +194,7 @@ void FieldInfo::LoadImage() { m_fileOpener.reset(); } if (m_texture == 0 && !m_filename.empty()) { - if (!LoadImageImpl(m_filename)) m_filename.clear(); + if (!LoadImageImpl(m_filename.c_str())) m_filename.clear(); } } @@ -274,7 +276,7 @@ void FieldInfo::LoadJson(const wpi::Twine& jsonfile) { wpi::sys::path::append(pathname, image); // load field image - if (!LoadImageImpl(pathname)) return; + if (!LoadImageImpl(pathname.c_str())) return; // save to field info m_filename = pathname.str(); @@ -286,15 +288,16 @@ void FieldInfo::LoadJson(const wpi::Twine& jsonfile) { m_height = height; } -bool FieldInfo::LoadImageImpl(const wpi::Twine& fn) { +bool FieldInfo::LoadImageImpl(const char* fn) { wpi::outs() << "GUI: loading field image '" << fn << "'\n"; - GLuint oldTexture = m_texture; - if (!LoadTextureFromFile(fn, &m_texture, &m_imageWidth, &m_imageHeight)) { + auto oldTexture = m_texture; + if (!gui::LoadTextureFromFile(fn, &m_texture, &m_imageWidth, + &m_imageHeight)) { wpi::errs() << "GUI: could not read field image\n"; return false; } - if (oldTexture != 0) glDeleteTextures(1, &oldTexture); - m_filename = fn.str(); + if (oldTexture != 0) gui::DeleteTexture(oldTexture); + m_filename = fn; return true; } @@ -332,9 +335,8 @@ FieldFrameData FieldInfo::GetFrameData() const { void FieldInfo::Draw(ImDrawList* drawList, const ImVec2& windowPos, const FieldFrameData& ffd) const { if (m_texture != 0 && m_imageHeight != 0 && m_imageWidth != 0) { - drawList->AddImage( - reinterpret_cast(static_cast(m_texture)), - windowPos + ffd.imageMin, windowPos + ffd.imageMax); + drawList->AddImage(m_texture, windowPos + ffd.imageMin, + windowPos + ffd.imageMax); } // draw the field "active area" as a yellow boundary box @@ -379,7 +381,7 @@ void FieldInfo::WriteIni(ImGuiTextBuffer* out) const { } void RobotInfo::Reset() { - if (m_texture != 0) glDeleteTextures(1, &m_texture); + if (m_texture != 0) gui::DeleteTexture(m_texture); m_texture = 0; m_filename.clear(); } @@ -387,23 +389,23 @@ void RobotInfo::Reset() { void RobotInfo::LoadImage() { if (m_fileOpener && m_fileOpener->ready(0)) { auto result = m_fileOpener->result(); - if (!result.empty()) LoadImageImpl(result[0]); + if (!result.empty()) LoadImageImpl(result[0].c_str()); m_fileOpener.reset(); } if (m_texture == 0 && !m_filename.empty()) { - if (!LoadImageImpl(m_filename)) m_filename.clear(); + if (!LoadImageImpl(m_filename.c_str())) m_filename.clear(); } } -bool RobotInfo::LoadImageImpl(const wpi::Twine& fn) { +bool RobotInfo::LoadImageImpl(const char* fn) { wpi::outs() << "GUI: loading robot image '" << fn << "'\n"; - GLuint oldTexture = m_texture; - if (!LoadTextureFromFile(fn, &m_texture, nullptr, nullptr)) { + auto oldTexture = m_texture; + if (!gui::LoadTextureFromFile(fn, &m_texture, nullptr, nullptr)) { wpi::errs() << "GUI: could not read robot image\n"; return false; } - if (oldTexture != 0) glDeleteTextures(1, &oldTexture); - m_filename = fn.str(); + if (oldTexture != 0) gui::DeleteTexture(oldTexture); + m_filename = fn; return true; } @@ -470,8 +472,7 @@ void RobotInfo::Draw(ImDrawList* drawList, const ImVec2& windowPos, float hitRadius) const { if (m_texture != 0) { drawList->AddImageQuad( - reinterpret_cast(static_cast(m_texture)), - windowPos + rfd.corners[0], windowPos + rfd.corners[1], + m_texture, windowPos + rfd.corners[0], windowPos + rfd.corners[1], windowPos + rfd.corners[2], windowPos + rfd.corners[3]); } else { drawList->AddQuad(windowPos + rfd.corners[0], windowPos + rfd.corners[1], diff --git a/simulation/halsim_gui/src/main/native/cpp/GuiUtil.cpp b/simulation/halsim_gui/src/main/native/cpp/GuiUtil.cpp index 1b2640f706..8308dec58e 100644 --- a/simulation/halsim_gui/src/main/native/cpp/GuiUtil.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/GuiUtil.cpp @@ -7,45 +7,6 @@ #include "GuiUtil.h" -#include - -#include - -bool halsimgui::LoadTextureFromFile(const wpi::Twine& filename, - GLuint* out_texture, int* out_width, - int* out_height) { - wpi::SmallString<128> buf; - - // Load from file - int width = 0; - int height = 0; - unsigned char* data = - stbi_load(filename.toNullTerminatedStringRef(buf).data(), &width, &height, - nullptr, 4); - if (!data) return false; - - // Create a OpenGL texture identifier - GLuint texture; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - - // Setup filtering parameters for display - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Upload pixels into texture - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, data); - stbi_image_free(data); - - *out_texture = texture; - if (out_width) *out_width = width; - if (out_height) *out_height = height; - - return true; -} - void halsimgui::MaxFit(ImVec2* min, ImVec2* max, float width, float height) { float destWidth = max->x - min->x; float destHeight = max->y - min->y; diff --git a/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp index a141bdde4e..3cead74e8d 100644 --- a/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp @@ -8,21 +8,18 @@ #include "HALSimGui.h" #include -#include -#include -#include #include #include -#include -#include -#include #include #include #include +#include using namespace halsimgui; +namespace gui = wpi::gui; + namespace { struct WindowInfo { WindowInfo() = default; @@ -45,54 +42,18 @@ struct WindowInfo { }; } // namespace -static std::atomic_bool gExit{false}; -static GLFWwindow* gWindow; -static bool gWindowLoadedWidthHeight = false; -static int gWindowWidth = 1280; -static int gWindowHeight = 720; -static int gWindowMaximized = 0; -static int gWindowXPos = -1; -static int gWindowYPos = -1; -static std::vector> gInitializers; -static std::vector> gExecutors; static std::vector gWindows; static wpi::StringMap gWindowMap; // index into gWindows static std::vector gSortedWindows; // index into gWindows static std::vector> gOptionMenus; static std::vector> gMenus; -static int gUserScale = 2; -static int gStyle = 0; -static constexpr int kScaledFontLevels = 9; -static ImFont* gScaledFont[kScaledFontLevels]; static bool gDisableOutputsOnDSDisable = true; -static void glfw_error_callback(int error, const char* description) { - wpi::errs() << "GLFW Error " << error << ": " << description << '\n'; -} - -static void glfw_window_size_callback(GLFWwindow*, int width, int height) { - if (!gWindowMaximized) { - gWindowWidth = width; - gWindowHeight = height; - } -} - -static void glfw_window_maximize_callback(GLFWwindow* window, int maximized) { - gWindowMaximized = maximized; -} - -static void glfw_window_pos_callback(GLFWwindow* window, int xpos, int ypos) { - if (!gWindowMaximized) { - gWindowXPos = xpos; - gWindowYPos = ypos; - } -} - // read/write open state to ini file static void* SimWindowsReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name) { - if (wpi::StringRef{name} == "GLOBAL") return &gWindow; + if (wpi::StringRef{name} == "GLOBAL") return &gDisableOutputsOnDSDisable; int index = gWindowMap.try_emplace(name, gWindows.size()).first->second; if (index == static_cast(gWindows.size())) { @@ -111,26 +72,10 @@ static void SimWindowsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, name = name.trim(); value = value.trim(); - if (entry == &gWindow) { + if (entry == &gDisableOutputsOnDSDisable) { int num; if (value.getAsInteger(10, num)) return; - if (name == "width") { - gWindowWidth = num; - gWindowLoadedWidthHeight = true; - } else if (name == "height") { - gWindowHeight = num; - gWindowLoadedWidthHeight = true; - } else if (name == "maximized") { - gWindowMaximized = num; - } else if (name == "xpos") { - gWindowXPos = num; - } else if (name == "ypos") { - gWindowYPos = num; - } else if (name == "userScale") { - gUserScale = num; - } else if (name == "style") { - gStyle = num; - } else if (name == "disableOutputsOnDS") { + if (name == "disableOutputsOnDS") { gDisableOutputsOnDSDisable = num; } return; @@ -150,37 +95,20 @@ static void SimWindowsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, static void SimWindowsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf) { - out_buf->appendf( - "[SimWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n" - "xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\ndisableOutputsOnDS=%d\n", - gWindowWidth, gWindowHeight, gWindowMaximized, gWindowXPos, gWindowYPos, - gUserScale, gStyle, gDisableOutputsOnDSDisable ? 1 : 0); + out_buf->appendf("[SimWindow][GLOBAL]\ndisableOutputsOnDS=%d\n\n", + gDisableOutputsOnDSDisable ? 1 : 0); for (auto&& window : gWindows) out_buf->appendf("[SimWindow][%s]\nvisible=%d\nenabled=%d\n\n", window.name.c_str(), window.visible ? 1 : 0, window.enabled ? 1 : 0); } -static void UpdateStyle() { - switch (gStyle) { - case 0: - ImGui::StyleColorsClassic(); - break; - case 1: - ImGui::StyleColorsDark(); - break; - case 2: - ImGui::StyleColorsLight(); - break; - } -} - void HALSimGui::Add(std::function initialize) { - if (initialize) gInitializers.emplace_back(std::move(initialize)); + gui::AddInit(std::move(initialize)); } void HALSimGui::AddExecute(std::function execute) { - if (execute) gExecutors.emplace_back(std::move(execute)); + gui::AddEarlyExecute(std::move(execute)); } void HALSimGui::AddWindow(const char* name, std::function display, @@ -262,196 +190,31 @@ bool HALSimGui::AreOutputsDisabled() { return gDisableOutputsOnDSDisable && !HALSIM_GetDriverStationEnabled(); } +void HALSimGui::GlobalInit() { gui::CreateContext(); } + bool HALSimGui::Initialize() { - // Setup window - glfwSetErrorCallback(glfw_error_callback); - glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); - if (!glfwInit()) return false; + gui::AddInit([] { + // Hook ini handler to save settings + ImGuiSettingsHandler iniHandler; + iniHandler.TypeName = "SimWindow"; + iniHandler.TypeHash = ImHashStr(iniHandler.TypeName); + iniHandler.ReadOpenFn = SimWindowsReadOpen; + iniHandler.ReadLineFn = SimWindowsReadLine; + iniHandler.WriteAllFn = SimWindowsWriteAll; + ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler); + }); - // Decide GL+GLSL versions -#if __APPLE__ - // GL 3.2 + GLSL 150 - const char* glsl_version = "#version 150"; - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac - glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_TRUE); -#else - // GL 3.0 + GLSL 130 - const char* glsl_version = "#version 130"; - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ - // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ -#endif - - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - (void)io; - - // Hook ini handler to save settings - ImGuiSettingsHandler iniHandler; - iniHandler.TypeName = "SimWindow"; - iniHandler.TypeHash = ImHashStr(iniHandler.TypeName); - iniHandler.ReadOpenFn = SimWindowsReadOpen; - iniHandler.ReadLineFn = SimWindowsReadLine; - iniHandler.WriteAllFn = SimWindowsWriteAll; - ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler); - - for (auto&& initialize : gInitializers) { - if (initialize) initialize(); - } - - // Load INI file - ImGui::LoadIniSettingsFromDisk(io.IniFilename); - - // Set initial window settings - glfwWindowHint(GLFW_MAXIMIZED, gWindowMaximized ? GLFW_TRUE : GLFW_FALSE); - - if (gWindowWidth == 0 || gWindowHeight == 0) { - gWindowWidth = 1280; - gWindowHeight = 720; - gWindowLoadedWidthHeight = false; - } - - float windowScale = 1.0; - if (!gWindowLoadedWidthHeight) { - 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 < gWindowWidth || monHeight < gWindowHeight) { - glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); - windowScale = (std::min)(monWidth * 1.0 / gWindowWidth, - monHeight * 1.0 / gWindowHeight); + gui::AddWindowScaler([](float windowScale) { + // scale default window positions + for (auto&& window : gWindows) { + if ((window.posCond & ImGuiCond_FirstUseEver) != 0) { + window.pos.x *= windowScale; + window.pos.y *= windowScale; } } - } - if (gWindowXPos != -1 && gWindowYPos != -1) - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - - // Create window with graphics context - gWindow = glfwCreateWindow(gWindowWidth, gWindowHeight, - "Robot Simulation GUI", nullptr, nullptr); - if (!gWindow) return false; - - if (!gWindowLoadedWidthHeight) { - if (windowScale == 1.0) - glfwGetWindowContentScale(gWindow, &windowScale, nullptr); - wpi::outs() << "windowScale = " << windowScale; - // force user scale if window scale is smaller - if (windowScale <= 0.5) - gUserScale = 0; - else if (windowScale <= 0.75) - gUserScale = 1; - if (windowScale != 1.0) { - // scale default window positions - for (auto&& window : gWindows) { - if ((window.posCond & ImGuiCond_FirstUseEver) != 0) { - window.pos.x *= windowScale; - window.pos.y *= windowScale; - } - } - } - } - - // Update window settings - if (gWindowXPos != -1 && gWindowYPos != -1) { - glfwSetWindowPos(gWindow, gWindowXPos, gWindowYPos); - glfwShowWindow(gWindow); - } - - // Set window callbacks - glfwGetWindowSize(gWindow, &gWindowWidth, &gWindowHeight); - glfwSetWindowSizeCallback(gWindow, glfw_window_size_callback); - glfwSetWindowMaximizeCallback(gWindow, glfw_window_maximize_callback); - glfwSetWindowPosCallback(gWindow, glfw_window_pos_callback); - - glfwMakeContextCurrent(gWindow); - glfwSwapInterval(1); // Enable vsync - - // Initialize OpenGL loader - if (gl3wInit() != 0) { - wpi::errs() << "Failed to initialize OpenGL loader!\n"; - return false; - } - - // Setup Dear ImGui style - UpdateStyle(); - - // Setup Platform/Renderer bindings - ImGui_ImplGlfw_InitForOpenGL(gWindow, true); - ImGui_ImplOpenGL3_Init(glsl_version); - - // Load Fonts - // - If no fonts are loaded, dear imgui will use the default font. You can - // also load multiple fonts and use ImGui::PushFont()/PopFont() to select - // them. - // - AddFontFromFileTTF() will return the ImFont* so you can store it if you - // need to select the font among multiple. - // - If the file cannot be loaded, the function will return NULL. Please - // handle those errors in your application (e.g. use an assertion, or display - // an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored - // into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which - // ImGui_ImplXXXX_NewFrame below will call. - // - Read 'misc/fonts/README.txt' for more instructions and details. - // - Remember that in C/C++ if you want to include a backslash \ in a string - // literal you need to write a double backslash \\ ! - // io.Fonts->AddFontDefault(); - // io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - // io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - // io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - // io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); - // ImFont* font = - // io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, - // NULL, io.Fonts->GetGlyphRangesJapanese()); IM_ASSERT(font != NULL); - - // this range is based on 13px being the "nominal" 100% size and going from - // ~0.5x (7px) to ~2.0x (25px) - for (int i = 0; i < kScaledFontLevels; ++i) { - float size = 7.0f + i * 3.0f; - ImFontConfig cfg; - std::snprintf(cfg.Name, sizeof(cfg.Name), "ProggyDotted-%d", - static_cast(size)); - gScaledFont[i] = ImGui::AddFontProggyDotted(io, size, &cfg); - } - - return true; -} - -void HALSimGui::Main(void*) { - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - - // Main loop - while (!glfwWindowShouldClose(gWindow) && !gExit) { - // Poll and handle events (inputs, window resize, etc.) - glfwPollEvents(); - - // Start the Dear ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - // Scale based on OS window content scaling - float windowScale = 1.0; - glfwGetWindowContentScale(gWindow, &windowScale, nullptr); - // 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 = - std::clamp(gUserScale + static_cast((windowScale - 1.0) * 4), 0, - kScaledFontLevels - 1); - ImGui::GetIO().FontDefault = gScaledFont[fontScale]; - - for (auto&& execute : gExecutors) { - if (execute) execute(); - } + }); + gui::AddLateExecute([] { { ImGui::BeginMainMenuBar(); @@ -464,41 +227,7 @@ void HALSimGui::Main(void*) { ImGui::EndMenu(); } - if (ImGui::BeginMenu("View")) { - if (ImGui::BeginMenu("Style")) { - bool selected; - selected = gStyle == 0; - if (ImGui::MenuItem("Classic", nullptr, &selected, true)) { - gStyle = 0; - UpdateStyle(); - } - selected = gStyle == 1; - if (ImGui::MenuItem("Dark", nullptr, &selected, true)) { - gStyle = 1; - UpdateStyle(); - } - selected = gStyle == 2; - if (ImGui::MenuItem("Light", nullptr, &selected, true)) { - gStyle = 2; - UpdateStyle(); - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Zoom")) { - for (int i = 0; i < kScaledFontLevels && (25 * (i + 2)) <= 200; ++i) { - char label[20]; - std::snprintf(label, sizeof(label), "%d%%", 25 * (i + 2)); - bool selected = gUserScale == i; - bool enabled = (fontScale - gUserScale + i) >= 0 && - (fontScale - gUserScale + i) < kScaledFontLevels; - if (ImGui::MenuItem(label, nullptr, &selected, enabled)) - gUserScale = i; - } - ImGui::EndMenu(); - } - ImGui::EndMenu(); - } + gui::EmitViewMenu(); if (ImGui::BeginMenu("Window")) { for (auto&& windowIndex : gSortedWindows) { @@ -539,29 +268,16 @@ void HALSimGui::Main(void*) { if (window.setPadding) ImGui::PopStyleVar(); } } + }); - // Rendering - ImGui::Render(); - int display_w, display_h; - glfwGetFramebufferSize(gWindow, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + if (!gui::Initialize("", 1280, 720)) return false; - glfwSwapBuffers(gWindow); - } - - // Cleanup - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - glfwDestroyWindow(gWindow); - glfwTerminate(); + return true; } -void HALSimGui::Exit(void*) { gExit = true; } +void HALSimGui::Main(void*) { gui::Main(); } + +void HALSimGui::Exit(void*) { gui::Exit(); } extern "C" { diff --git a/simulation/halsim_gui/src/main/native/cpp/main.cpp b/simulation/halsim_gui/src/main/native/cpp/main.cpp index abd4e2e362..7c1541e0dc 100644 --- a/simulation/halsim_gui/src/main/native/cpp/main.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/main.cpp @@ -36,6 +36,7 @@ extern "C" { __declspec(dllexport) #endif int HALSIM_InitExtension(void) { + HALSimGui::GlobalInit(); HALSimGui::Add(AccelerometerGui::Initialize); HALSimGui::Add(AddressableLEDGui::Initialize); HALSimGui::Add(AnalogGyroGui::Initialize); diff --git a/simulation/halsim_gui/src/main/native/include/GuiUtil.h b/simulation/halsim_gui/src/main/native/include/GuiUtil.h index 08850dbdf3..992ef5c023 100644 --- a/simulation/halsim_gui/src/main/native/include/GuiUtil.h +++ b/simulation/halsim_gui/src/main/native/include/GuiUtil.h @@ -7,15 +7,10 @@ #pragma once -#include #include -#include namespace halsimgui { -bool LoadTextureFromFile(const wpi::Twine& filename, GLuint* out_texture, - int* out_width, int* out_height); - // get distance^2 between two ImVec's inline float GetDistSquared(const ImVec2& a, const ImVec2& b) { float deltaX = b.x - a.x; diff --git a/simulation/halsim_gui/src/main/native/include/HALSimGui.h b/simulation/halsim_gui/src/main/native/include/HALSimGui.h index 7a710d100c..4a4c912002 100644 --- a/simulation/halsim_gui/src/main/native/include/HALSimGui.h +++ b/simulation/halsim_gui/src/main/native/include/HALSimGui.h @@ -34,6 +34,7 @@ namespace halsimgui { class HALSimGui { public: + static void GlobalInit(); static bool Initialize(); static void Main(void*); static void Exit(void*);