diff --git a/.gitmodules b/.gitmodules index e96caf6d..dd520028 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ [submodule "deps/glm"] path = deps/glm url = https://github.com/g-truc/glm.git +[submodule "deps/imgui/implot"] + path = deps/imgui/implot + url = https://github.com/epezent/implot diff --git a/deps/imgui/CMakeLists.txt b/deps/imgui/CMakeLists.txt index ec877e5f..24576087 100644 --- a/deps/imgui/CMakeLists.txt +++ b/deps/imgui/CMakeLists.txt @@ -19,7 +19,14 @@ endif() if("${POLYSCOPE_BACKEND_OPENGL3_GLFW}") - set(SRCS imgui/imgui.cpp imgui/imgui_draw.cpp imgui/imgui_tables.cpp imgui/imgui_widgets.cpp imgui/imgui_demo.cpp imgui/backends/imgui_impl_glfw.cpp imgui/backends/imgui_impl_opengl3.cpp) + # imgui sources + list(APPEND SRCS imgui/imgui.cpp imgui/imgui_draw.cpp imgui/imgui_tables.cpp imgui/imgui_widgets.cpp imgui/imgui_demo.cpp imgui/backends/imgui_impl_glfw.cpp imgui/backends/imgui_impl_opengl3.cpp) + + # implot sources + list(APPEND SRCS + implot/implot.cpp + implot/implot_items.cpp + ) add_library( imgui @@ -27,6 +34,7 @@ if("${POLYSCOPE_BACKEND_OPENGL3_GLFW}") ) target_include_directories(imgui PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/imgui/") + target_include_directories(imgui PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/implot/") target_include_directories(imgui PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../glfw/include/") target_include_directories(imgui PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../glad/include/") diff --git a/deps/imgui/implot b/deps/imgui/implot new file mode 160000 index 00000000..3da8bd34 --- /dev/null +++ b/deps/imgui/implot @@ -0,0 +1 @@ +Subproject commit 3da8bd34299965d3b0ab124df743fe3e076fa222 diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index 0eaaef66..eb9c2181 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -92,7 +92,8 @@ void constructDemoCurveNetwork(std::string curveName, std::vector nod valYabs[iE] = std::fabs(midpointPos.y); } polyscope::getCurveNetwork(curveName)->addEdgeScalarQuantity("edge len", edgeLen, polyscope::DataType::MAGNITUDE); - polyscope::getCurveNetwork(curveName)->addEdgeScalarQuantity("edge valYabs", valYabs, polyscope::DataType::MAGNITUDE); + polyscope::getCurveNetwork(curveName)->addEdgeScalarQuantity("edge valYabs", valYabs, + polyscope::DataType::MAGNITUDE); polyscope::getCurveNetwork(curveName)->addEdgeScalarQuantity("edge categorical", valEdgeCat, polyscope::DataType::CATEGORICAL); polyscope::getCurveNetwork(curveName)->addEdgeColorQuantity("eColor", randColor); @@ -845,6 +846,9 @@ void callback() { } } + if (ImGui::Button("nested show")) { + polyscope::show(); + } if (ImGui::Button("drop camera view here")) { dropCameraView(); @@ -858,9 +862,28 @@ void callback() { addVolumeGrid(); } + // ImPlot + // dummy data + if (ImGui::TreeNode("ImPlot")) { + + std::vector plotVals; + for (float t = 0; t < 10.; t += 0.01) { + plotVals.push_back(std::cosf(t + ImGui::GetTime())); + } + + // sample plot + if (ImPlot::BeginPlot("test plot")) { + ImPlot::PlotLine("sample_val", &plotVals.front(), plotVals.size()); + ImPlot::EndPlot(); + } + + ImGui::TreePop(); + } + ImGui::PopItemWidth(); } + int main(int argc, char** argv) { // Configure the argument parser args::ArgumentParser parser("A simple demo of Polyscope.\nBy " diff --git a/include/polyscope/polyscope.h b/include/polyscope/polyscope.h index 9adbcbf4..070a434f 100644 --- a/include/polyscope/polyscope.h +++ b/include/polyscope/polyscope.h @@ -9,6 +9,7 @@ #include #include "imgui.h" +#include "implot.h" #include "polyscope/context.h" #include "polyscope/group.h" diff --git a/src/polyscope.cpp b/src/polyscope.cpp index b18377d7..26b6e894 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -8,6 +8,7 @@ #include #include "imgui.h" +#include "implot.h" #include "polyscope/options.h" #include "polyscope/pick.h" @@ -33,6 +34,7 @@ namespace { // initialization. struct ContextEntry { ImGuiContext* context; + ImPlotContext* plotContext; std ::function callback; bool drawDefaultUI; }; @@ -183,7 +185,7 @@ void init(std::string backend) { // Create an initial context based context. Note that calling show() never actually uses this context, because it // pushes a new one each time. But using frameTick() may use this context. - contextStack.push_back(ContextEntry{ImGui::GetCurrentContext(), nullptr, true}); + contextStack.push_back(ContextEntry{ImGui::GetCurrentContext(), ImPlot::GetCurrentContext(), nullptr, true}); view::invalidateView(); @@ -205,11 +207,13 @@ void pushContext(std::function callbackFunction, bool drawDefaultUI) { // Create a new context and push it on to the stack ImGuiContext* newContext = ImGui::CreateContext(); + ImPlotContext* newPlotContext = ImPlot::CreateContext(); ImGuiIO& oldIO = ImGui::GetIO(); // used to GLFW + OpenGL data to the new IO object #ifdef IMGUI_HAS_DOCK ImGuiPlatformIO& oldPlatformIO = ImGui::GetPlatformIO(); #endif ImGui::SetCurrentContext(newContext); + ImPlot::SetCurrentContext(newPlotContext); #ifdef IMGUI_HAS_DOCK // Propagate GLFW window handle to new context ImGui::GetMainViewport()->PlatformHandle = oldPlatformIO.Viewports[0]->PlatformHandle; @@ -219,7 +223,8 @@ void pushContext(std::function callbackFunction, bool drawDefaultUI) { render::engine->configureImGui(); - contextStack.push_back(ContextEntry{newContext, callbackFunction, drawDefaultUI}); + + contextStack.push_back(ContextEntry{newContext, newPlotContext, callbackFunction, drawDefaultUI}); if (contextStack.size() > 50) { // Catch bugs with nested show() @@ -259,14 +264,17 @@ void pushContext(std::function callbackFunction, bool drawDefaultUI) { // Workaround overzealous ImGui assertion before destroying any inner context // https://github.com/ocornut/imgui/pull/7175 ImGui::SetCurrentContext(newContext); + ImPlot::SetCurrentContext(newPlotContext); ImGui::GetIO().BackendPlatformUserData = nullptr; ImGui::GetIO().BackendRendererUserData = nullptr; + ImPlot::DestroyContext(newPlotContext); ImGui::DestroyContext(newContext); // Restore the previous context, if there was one if (!contextStack.empty()) { ImGui::SetCurrentContext(contextStack.back().context); + ImPlot::SetCurrentContext(contextStack.back().plotContext); } } diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index 405c8366..3fa63568 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -1576,6 +1576,7 @@ void MockGLEngine::initialize() { void MockGLEngine::initializeImGui() { ImGui::CreateContext(); // must call once at start + ImPlot::CreateContext(); configureImGui(); } @@ -1597,7 +1598,10 @@ void MockGLEngine::shutdown() { shutdownImGui(); } -void MockGLEngine::shutdownImGui() { ImGui::DestroyContext(); } +void MockGLEngine::shutdownImGui() { + ImPlot::DestroyContext(); + ImGui::DestroyContext(); +} void MockGLEngine::swapDisplayBuffers() {} diff --git a/src/render/opengl/gl_engine_egl.cpp b/src/render/opengl/gl_engine_egl.cpp index 71a16c0b..151916d4 100644 --- a/src/render/opengl/gl_engine_egl.cpp +++ b/src/render/opengl/gl_engine_egl.cpp @@ -437,6 +437,7 @@ void GLEngineEGL::initializeImGui() { // functions ImGui::CreateContext(); + ImPlot::CreateContext(); configureImGui(); } diff --git a/src/render/opengl/gl_engine_glfw.cpp b/src/render/opengl/gl_engine_glfw.cpp index 9333931c..5f49aa20 100644 --- a/src/render/opengl/gl_engine_glfw.cpp +++ b/src/render/opengl/gl_engine_glfw.cpp @@ -148,6 +148,7 @@ void GLEngineGLFW::initializeImGui() { bindDisplay(); ImGui::CreateContext(); // must call once at start + ImPlot::CreateContext(); // Set up ImGUI glfw bindings ImGui_ImplGlfw_InitForOpenGL(mainWindow, true); @@ -197,6 +198,7 @@ void GLEngineGLFW::shutdownImGui() { // ImGui shutdown things ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); + ImPlot::DestroyContext(); ImGui::DestroyContext(); } diff --git a/src/screenshot.cpp b/src/screenshot.cpp index b9ee602c..e65ff92e 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -47,18 +47,29 @@ std::vector getRenderInBuffer(const ScreenshotOptions& options = bool requestedAlready = redrawRequested(); requestRedraw(); + // There's a ton of junk needed here to handle the includeUI case... // Create a new context and push it on to the stack + // FIXME this solution doesn't really work, it forgets UI state like which nodes were open, scrolled setting, etc. + // I'm not sure if it's possible to do this like we want in ImGui. The alternate solution would be to save the render + // from the previous render pass, but I think that comes with other problems on the Polyscope side. I'm not sure what + // the answer is. ImGuiContext* oldContext; ImGuiContext* newContext; + ImPlotContext* oldPlotContext; + ImPlotContext* newPlotContext; if (options.includeUI) { // WARNING: code duplicated here and in pushContext() oldContext = ImGui::GetCurrentContext(); newContext = ImGui::CreateContext(); + oldPlotContext = ImPlot::GetCurrentContext(); + newPlotContext = ImPlot::CreateContext(); ImGuiIO& oldIO = ImGui::GetIO(); // used to GLFW + OpenGL data to the new IO object #ifdef IMGUI_HAS_DOCK ImGuiPlatformIO& oldPlatformIO = ImGui::GetPlatformIO(); #endif ImGui::SetCurrentContext(newContext); + ImPlot::SetCurrentContext(newPlotContext); + #ifdef IMGUI_HAS_DOCK // Propagate GLFW window handle to new context ImGui::GetMainViewport()->PlatformHandle = oldPlatformIO.Viewports[0]->PlatformHandle; @@ -81,11 +92,15 @@ std::vector getRenderInBuffer(const ScreenshotOptions& options = // Workaround overzealous ImGui assertion before destroying any inner context // https://github.com/ocornut/imgui/pull/7175 ImGui::SetCurrentContext(newContext); + ImPlot::SetCurrentContext(newPlotContext); ImGui::GetIO().BackendPlatformUserData = nullptr; ImGui::GetIO().BackendRendererUserData = nullptr; + ImPlot::DestroyContext(newPlotContext); ImGui::DestroyContext(newContext); + ImGui::SetCurrentContext(oldContext); + ImPlot::SetCurrentContext(oldPlotContext); } diff --git a/test/src/basics_test.cpp b/test/src/basics_test.cpp index 80426277..1544705c 100644 --- a/test/src/basics_test.cpp +++ b/test/src/basics_test.cpp @@ -45,6 +45,7 @@ TEST_F(PolyscopeTest, FrameTickWithImgui) { polyscope::state::userCallback = nullptr; } + // We should be able to nest calls to show() via the callback. ImGUI causes headaches here TEST_F(PolyscopeTest, NestedShow) { @@ -148,6 +149,55 @@ TEST_F(PolyscopeTest, ScreenshotBuffer) { EXPECT_EQ(buff2.size(), polyscope::view::bufferWidth * polyscope::view::bufferHeight * 4); } +TEST_F(PolyscopeTest, ImPlotBasic) { + + std::vector xvals = {0., 2., 4., 8.}; + + auto showCallback = [&]() { + ImGui::Button("do something"); + if(ImPlot::BeginPlot("test plot")) { + ImPlot::PlotLine("test line", &xvals.front(), xvals.size()); + ImPlot::EndPlot(); + } + }; + polyscope::state::userCallback = showCallback; + + polyscope::show(3); + + for (int i = 0; i < 3; i++) { + polyscope::frameTick(); + } + + polyscope::state::userCallback = nullptr; +} + + +TEST_F(PolyscopeTest, ImPlotScreenshot) { + // test this because there is some context logic duplicated there + + std::vector xvals = {0., 2., 4., 8.}; + + auto showCallback = [&]() { + ImGui::Button("do something"); + if(ImPlot::BeginPlot("test plot")) { + ImPlot::PlotLine("test line", &xvals.front(), xvals.size()); + ImPlot::EndPlot(); + } + }; + polyscope::state::userCallback = showCallback; + + polyscope::show(3); + + polyscope::ScreenshotOptions opts; + opts.includeUI = true; + opts.transparentBackground = false; + polyscope::screenshot(opts); + + polyscope::show(3); + + polyscope::state::userCallback = nullptr; +} + // ============================================================ // =============== View and navigation // ============================================================