diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index afa339b8..54aeb071 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -780,7 +780,7 @@ void callback() { static int loadedMat = 1; static bool depthClick = false; - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * polyscope::options::uiScale); ImGui::InputInt("num points", &numPoints); ImGui::InputFloat("param value", ¶m); diff --git a/include/polyscope/options.h b/include/polyscope/options.h index 627512a8..e2919ef2 100644 --- a/include/polyscope/options.h +++ b/include/polyscope/options.h @@ -93,6 +93,9 @@ extern std::string screenshotExtension; // sets the extension used for automatic // SSAA scaling in pixel multiples extern int ssaaFactor; +// DPI scaling to scale the UI on high-resolutoin screens +extern float uiScale; + // Transparency settings for the renderer extern TransparencyMode transparencyMode; extern int transparencyRenderPasses; diff --git a/include/polyscope/parameterization_quantity.ipp b/include/polyscope/parameterization_quantity.ipp index f7254e12..80f5028b 100644 --- a/include/polyscope/parameterization_quantity.ipp +++ b/include/polyscope/parameterization_quantity.ipp @@ -61,7 +61,7 @@ ParameterizationQuantity::ParameterizationQuantity(QuantityT& quantit template void ParameterizationQuantity::buildParameterizationUI() { - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); // Modulo stripey width if (ImGui::DragFloat("period", &checkerSize.get(), .001, 0.0001, 1.0, "%.4f", @@ -82,7 +82,7 @@ void ParameterizationQuantity::buildParameterizationUI() { setCheckerColors(getCheckerColors()); break; case ParamVizStyle::CHECKER_ISLANDS: - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (ImGui::DragFloat("alt darkness", &altDarkness.get(), 0.01, 0., 1.)) { altDarkness.manuallyChanged(); requestRedraw(); @@ -103,7 +103,7 @@ void ParameterizationQuantity::buildParameterizationUI() { case ParamVizStyle::LOCAL_CHECK: case ParamVizStyle::LOCAL_RAD: { // Angle slider - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); ImGui::SliderAngle("angle shift", &localRot, -180, 180); // displays in degrees, works in radians TODO refresh/update/persist if (ImGui::DragFloat("alt darkness", &altDarkness.get(), 0.01, 0., 1.)) { diff --git a/include/polyscope/render/engine.h b/include/polyscope/render/engine.h index c37d3f86..f33f7748 100644 --- a/include/polyscope/render/engine.h +++ b/include/polyscope/render/engine.h @@ -518,7 +518,6 @@ class Engine { virtual void ImGuiRender() = 0; void setImGuiStyle(); - ImFontAtlas* getImGuiGlobalFontAtlas(); // Display an ImGui window showing a texture // WARNING: you must ensure that the texture buffer pointer stays valid until after the ImGui frame is rendered, which @@ -629,7 +628,7 @@ class Engine { bool useAltDisplayBuffer = false; // if true, push final render results offscreen to the alt buffer instead // Internal windowing and engine details - ImFontAtlas* globalFontAtlas = nullptr; + virtual void configureImGui() {}; // generates font things ImFont* regularFont = nullptr; ImFont* monoFont = nullptr; FrameBuffer* currRenderFramebuffer = nullptr; @@ -658,7 +657,6 @@ class Engine { TransparencyMode currLightingTransparencyMode = TransparencyMode::None; // Helpers - void configureImGui(); void loadDefaultMaterials(); void loadDefaultMaterial(std::string name); std::shared_ptr loadMaterialTexture(float* data, int width, int height); diff --git a/include/polyscope/render/mock_opengl/mock_gl_engine.h b/include/polyscope/render/mock_opengl/mock_gl_engine.h index f9d512f5..bcdb7e3e 100644 --- a/include/polyscope/render/mock_opengl/mock_gl_engine.h +++ b/include/polyscope/render/mock_opengl/mock_gl_engine.h @@ -358,6 +358,7 @@ class MockGLEngine : public Engine { // ImGui void initializeImGui() override; + void configureImGui() override; void shutdownImGui() override; void ImGuiNewFrame() override; void ImGuiRender() override; diff --git a/include/polyscope/render/opengl/gl_engine_egl.h b/include/polyscope/render/opengl/gl_engine_egl.h index 7948436a..21897c5f 100644 --- a/include/polyscope/render/opengl/gl_engine_egl.h +++ b/include/polyscope/render/opengl/gl_engine_egl.h @@ -69,6 +69,7 @@ class GLEngineEGL : public GLEngine { // === ImGui void initializeImGui() override; + void configureImGui() override; void shutdownImGui() override; void ImGuiNewFrame() override; void ImGuiRender() override; diff --git a/include/polyscope/render/opengl/gl_engine_glfw.h b/include/polyscope/render/opengl/gl_engine_glfw.h index 574f2b27..a86eba99 100644 --- a/include/polyscope/render/opengl/gl_engine_glfw.h +++ b/include/polyscope/render/opengl/gl_engine_glfw.h @@ -77,10 +77,13 @@ class GLEngineGLFW : public GLEngine { void shutdownImGui() override; void ImGuiNewFrame() override; void ImGuiRender() override; + void configureImGui() override; protected: // Internal windowing and engine details GLFWwindow* mainWindow = nullptr; + + void setUIScaleFromSystemDPI(); }; } // namespace backend_openGL3 diff --git a/include/polyscope/scalar_quantity.ipp b/include/polyscope/scalar_quantity.ipp index 00089bea..bff31d15 100644 --- a/include/polyscope/scalar_quantity.ipp +++ b/include/polyscope/scalar_quantity.ipp @@ -147,7 +147,7 @@ void ScalarQuantity::buildScalarUI() { // Isolines if (isolinesEnabled.get()) { - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); auto styleName = [](const IsolineStyle& m) -> std::string { diff --git a/src/camera_view.cpp b/src/camera_view.cpp index 5f3c708b..872059d3 100644 --- a/src/camera_view.cpp +++ b/src/camera_view.cpp @@ -366,7 +366,7 @@ void CameraView::buildCustomUI() { void CameraView::buildCustomOptionsUI() { - ImGui::PushItemWidth(150); + ImGui::PushItemWidth(150 * options::uiScale); if (widgetFocalLengthUpper == -777) widgetFocalLengthUpper = 2. * (*widgetFocalLength.get().getValuePtr()); if (ImGui::SliderFloat("widget focal length", widgetFocalLength.get().getValuePtr(), 0, widgetFocalLengthUpper, diff --git a/src/curve_network.cpp b/src/curve_network.cpp index f9b917b5..de1e5198 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -402,7 +402,7 @@ void CurveNetwork::buildCustomUI() { setColor(getColor()); } ImGui::SameLine(); - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (ImGui::SliderFloat("Radius", radius.get().getValuePtr(), 0.0, .1, "%.5f", ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_NoRoundToFormat)) { radius.manuallyChanged(); diff --git a/src/file_helpers.cpp b/src/file_helpers.cpp index 52371c38..7b45b16a 100644 --- a/src/file_helpers.cpp +++ b/src/file_helpers.cpp @@ -14,7 +14,7 @@ void filenamePromptCallback(char* buff, size_t len) { static bool windowOpen = true; ImGui::Begin("Enter filename", &windowOpen, ImGuiWindowFlags_AlwaysAutoResize); - ImGui::PushItemWidth(500); + ImGui::PushItemWidth(500 * options::uiScale); ImGui::InputText("##filename", buff, len); if (ImGui::Button("Ok")) { diff --git a/src/image_quantity_base.cpp b/src/image_quantity_base.cpp index e3f8e4fe..2d5c761f 100644 --- a/src/image_quantity_base.cpp +++ b/src/image_quantity_base.cpp @@ -114,7 +114,7 @@ void ImageQuantity::buildImageUI() { if (getShowFullscreen()) { - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (ImGui::SliderFloat("transparency", &transparency.get(), 0.f, 1.f)) { transparency.manuallyChanged(); requestRedraw(); diff --git a/src/imgui_config.cpp b/src/imgui_config.cpp index bc6df143..928e5f8c 100644 --- a/src/imgui_config.cpp +++ b/src/imgui_config.cpp @@ -1,6 +1,7 @@ // Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run #include "polyscope/imgui_config.h" +#include namespace polyscope { @@ -17,11 +18,11 @@ void configureImGuiStyle() { // Style ImGuiStyle* style = &ImGui::GetStyle(); - style->WindowRounding = 1; - style->FrameRounding = 1; - style->FramePadding.y = 4; - style->ScrollbarRounding = 1; - style->ScrollbarSize = 20; + style->WindowRounding = 1 * options::uiScale; + style->FrameRounding = 1 * options::uiScale; + style->FramePadding.y = 4 * options::uiScale; + style->ScrollbarRounding = 1 * options::uiScale; + style->ScrollbarSize = 20 * options::uiScale; // Colors @@ -77,28 +78,27 @@ std::tuple prepareImGuiFonts() { ImGuiIO& io = ImGui::GetIO(); // outputs - ImFontAtlas* globalFontAtlas; + ImFontAtlas* fontAtlas; ImFont* regularFont; ImFont* monoFont; { // add regular font ImFontConfig config; regularFont = io.Fonts->AddFontFromMemoryCompressedTTF(render::getLatoRegularCompressedData(), - render::getLatoRegularCompressedSize(), 18.0f, &config); + render::getLatoRegularCompressedSize(), options::uiScale*18.0f, &config); } { // add mono font ImFontConfig config; monoFont = io.Fonts->AddFontFromMemoryCompressedTTF(render::getCousineRegularCompressedData(), - render::getCousineRegularCompressedSize(), 16.0f, &config); + render::getCousineRegularCompressedSize(), options::uiScale*16.0f, &config); } // io.Fonts->AddFontFromFileTTF("test-font-name.ttf", 16); io.Fonts->Build(); - globalFontAtlas = io.Fonts; - return std::tuple{globalFontAtlas, regularFont, monoFont}; + return std::tuple{fontAtlas, regularFont, monoFont}; } } // namespace polyscope diff --git a/src/messages.cpp b/src/messages.cpp index 3f4fc021..69770cbb 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -174,7 +174,7 @@ void buildWarningUI(std::string warningBaseString, std::string warningDetailStri } // Nice button sizing - float buttonWidth = 120; + float buttonWidth = 120 * options::uiScale; float buttonOffset = (warningModalSize.x - buttonWidth) / 2.0; buttonOffset = std::max(buttonOffset, 0.0f); doIndent = buttonOffset > 0; diff --git a/src/options.cpp b/src/options.cpp index a97ec2ba..b3ce2558 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -42,6 +42,7 @@ float shadowDarkness = 0.25; // Rendering options +float uiScale = -1.0; // unset, must be set manually or during initialization int ssaaFactor = 1; // Transparency diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index a947a115..93f7600a 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -266,7 +266,7 @@ void PointCloud::buildCustomUI() { setPointColor(getPointColor()); } ImGui::SameLine(); - ImGui::PushItemWidth(70); + ImGui::PushItemWidth(70 * options::uiScale); if (ImGui::SliderFloat("Radius", pointRadius.get().getValuePtr(), 0.0, .1, "%.5f", ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_NoRoundToFormat)) { pointRadius.manuallyChanged(); diff --git a/src/polyscope.cpp b/src/polyscope.cpp index 037eeebd..15933e89 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -47,8 +47,10 @@ bool unshowRequested = false; float imguiStackMargin = 10; float lastWindowHeightPolyscope = 200; float lastWindowHeightUser = 200; -float leftWindowsWidth = 305; -float rightWindowsWidth = 500; +constexpr float LEFT_WINDOWS_WIDTH = 305; +constexpr float RIGHT_WINDOWS_WIDTH = 500; +float leftWindowsWidth = LEFT_WINDOWS_WIDTH; +float rightWindowsWidth = RIGHT_WINDOWS_WIDTH; auto lastMainLoopIterTime = std::chrono::steady_clock::now(); @@ -82,6 +84,10 @@ void readPrefsFile() { int val = prefsJSON["windowPosY"]; if (val >= 0 && val < 10000) view::initWindowPosY = val; } + if (prefsJSON.count("uiScale") > 0) { + float val = prefsJSON["uiScale"]; + if (val >= 0.25 && val <= 4.0) options::uiScale = val; + } } } @@ -98,6 +104,7 @@ void writePrefsFile() { std::tie(posX, posY) = render::engine->getWindowPos(); int windowWidth = view::windowWidth; int windowHeight = view::windowHeight; + float uiScale = options::uiScale; // Validate values. Don't write the prefs file if any of these values are obviously bogus (this seems to happen at // least on Windows when the application is minimzed) @@ -106,6 +113,7 @@ void writePrefsFile() { valuesValid &= posY >= 0 && posY < 10000; valuesValid &= windowWidth >= 64 && windowWidth < 10000; valuesValid &= windowHeight >= 64 && windowHeight < 10000; + valuesValid &= uiScale >= 0.25 && uiScale <= 4.; if (!valuesValid) return; // Build json object @@ -114,6 +122,7 @@ void writePrefsFile() { {"windowHeight", windowHeight}, {"windowPosX", posX}, {"windowPosY", posY}, + {"uiScale", uiScale}, }; // Write out json object @@ -181,7 +190,7 @@ bool isInitialized() { return state::initialized; } void pushContext(std::function callbackFunction, bool drawDefaultUI) { // Create a new context and push it on to the stack - ImGuiContext* newContext = ImGui::CreateContext(render::engine->getImGuiGlobalFontAtlas()); + ImGuiContext* newContext = ImGui::CreateContext(); ImGuiIO& oldIO = ImGui::GetIO(); // used to GLFW + OpenGL data to the new IO object #ifdef IMGUI_HAS_DOCK ImGuiPlatformIO& oldPlatformIO = ImGui::GetPlatformIO(); @@ -194,9 +203,7 @@ void pushContext(std::function callbackFunction, bool drawDefaultUI) { ImGui::GetIO().BackendPlatformUserData = oldIO.BackendPlatformUserData; ImGui::GetIO().BackendRendererUserData = oldIO.BackendRendererUserData; - if (options::configureImGuiStyleCallback) { - options::configureImGuiStyleCallback(); - } + render::engine->configureImGui(); contextStack.push_back(ContextEntry{newContext, callbackFunction, drawDefaultUI}); @@ -435,7 +442,6 @@ void renderSlicePlanes() { } void renderScene() { - processLazyProperties(); render::engine->applyTransparencySettings(); @@ -554,7 +560,7 @@ void userGuiBegin() { void userGuiEnd() { if (options::userGuiIsOnRightSide) { - rightWindowsWidth = ImGui::GetWindowWidth(); + rightWindowsWidth = RIGHT_WINDOWS_WIDTH * options::uiScale; lastWindowHeightUser = imguiStackMargin + ImGui::GetWindowHeight(); } else { lastWindowHeightUser = 0; @@ -645,7 +651,7 @@ void buildPolyscopeGui() { ImGui::Text("Rolling: %.1f ms/frame (%.1f fps)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); ImGui::Text("Last: %.1f ms/frame (%.1f fps)", ImGui::GetIO().DeltaTime * 1000.f, 1.f / ImGui::GetIO().DeltaTime); - ImGui::PushItemWidth(40); + ImGui::PushItemWidth(40 * options::uiScale); if (ImGui::InputInt("max fps", &options::maxFPS, 0)) { if (options::maxFPS < 1 && options::maxFPS != -1) { options::maxFPS = -1; @@ -679,7 +685,7 @@ void buildPolyscopeGui() { lastWindowHeightPolyscope = imguiStackMargin + ImGui::GetWindowHeight(); - leftWindowsWidth = ImGui::GetWindowWidth(); + leftWindowsWidth = LEFT_WINDOWS_WIDTH * options::uiScale; ImGui::End(); } @@ -748,7 +754,7 @@ void buildStructureGui() { ImGui::PopID(); } - leftWindowsWidth = ImGui::GetWindowWidth(); + leftWindowsWidth = LEFT_WINDOWS_WIDTH * options::uiScale; ImGui::End(); } @@ -767,7 +773,7 @@ void buildPickGui() { ImGui::Separator(); selection.first->buildPickUI(selection.second); - rightWindowsWidth = ImGui::GetWindowWidth(); + rightWindowsWidth = RIGHT_WINDOWS_WIDTH * options::uiScale; ImGui::End(); } } @@ -800,7 +806,6 @@ void buildUserGuiAndInvokeCallback() { } void draw(bool withUI, bool withContextCallback) { - processLazyProperties(); // Update buffer and context render::engine->makeContextCurrent(); @@ -850,8 +855,6 @@ void draw(bool withUI, bool withContextCallback) { (contextStack.back().callback)(); } - processLazyProperties(); - // Draw structures in the scene if (redrawNextFrame || options::alwaysRedraw) { renderScene(); @@ -1224,6 +1227,7 @@ namespace lazy { TransparencyMode transparencyMode = TransparencyMode::None; int transparencyRenderPasses = 8; int ssaaFactor = 1; +float uiScale = -1.; bool groundPlaneEnabled = true; GroundPlaneMode groundPlaneMode = GroundPlaneMode::TileReflection; ScaledValue groundPlaneHeightFactor = 0; @@ -1260,6 +1264,12 @@ void processLazyProperties() { lazy::ssaaFactor = options::ssaaFactor; render::engine->setSSAAFactor(options::ssaaFactor); } + + // uiScale + if (lazy::uiScale != options::uiScale) { + lazy::uiScale = options::uiScale; + render::engine->configureImGui(); + } // ground plane if (lazy::groundPlaneEnabled != options::groundPlaneEnabled || lazy::groundPlaneMode != options::groundPlaneMode) { diff --git a/src/render/color_maps.cpp b/src/render/color_maps.cpp index bfe0fb7c..79a0dc66 100644 --- a/src/render/color_maps.cpp +++ b/src/render/color_maps.cpp @@ -6,6 +6,7 @@ #include "polyscope/render/engine.h" #include "imgui.h" +#include "polyscope/polyscope.h" namespace polyscope { @@ -17,7 +18,7 @@ namespace render { bool buildColormapSelector(std::string& cm, std::string fieldName) { bool changed = false; - ImGui::PushItemWidth(125); + ImGui::PushItemWidth(125 * options::uiScale); if (ImGui::BeginCombo(fieldName.c_str(), cm.c_str())) { for (auto& c : render::engine->colorMaps) { diff --git a/src/render/engine.cpp b/src/render/engine.cpp index 819eb959..34afd921 100644 --- a/src/render/engine.cpp +++ b/src/render/engine.cpp @@ -280,7 +280,7 @@ void Engine::buildEngineGui() { if (ImGui::TreeNode("Appearance")) { // == Display - ImGui::PushItemWidth(120); + ImGui::PushItemWidth(120 * options::uiScale); // ImGui::Text("Background"); // ImGui::SameLine(); static std::string displayBackgroundName = "None"; @@ -349,13 +349,19 @@ void Engine::buildEngineGui() { // == Anti-aliasing ImGui::SetNextItemOpen(false, ImGuiCond_FirstUseEver); - if (ImGui::TreeNode("Anti-Aliasing")) { + if (ImGui::TreeNode("Anti-Aliasing & DPI")) { if (ImGui::InputInt("SSAA (pretty)", &ssaaFactor, 1)) { ssaaFactor = std::min(ssaaFactor, 4); ssaaFactor = std::max(ssaaFactor, 1); options::ssaaFactor = ssaaFactor; requestRedraw(); } + + if (ImGui::InputFloat("UI Scale", &options::uiScale, 0.25f)) { + options::uiScale = std::min(options::uiScale, 4.f); + options::uiScale = std::max(options::uiScale, 0.25f); + requestRedraw(); + } ImGui::TreePop(); } @@ -477,7 +483,7 @@ void Engine::setScreenBufferViewports() { } bool Engine::bindSceneBuffer() { - setCurrentPixelScaling(ssaaFactor); + setCurrentPixelScaling(ssaaFactor * options::uiScale); return sceneBuffer->bindForRendering(); } @@ -1093,18 +1099,6 @@ const ValueColorMap& Engine::getColorMap(const std::string& name) { } -void Engine::configureImGui() { - - if (options::prepareImGuiFontsCallback) { - std::tie(globalFontAtlas, regularFont, monoFont) = options::prepareImGuiFontsCallback(); - } - - - if (options::configureImGuiStyleCallback) { - options::configureImGuiStyleCallback(); - } -} - void Engine::loadDefaultColorMap(std::string name) { const std::vector* buff = nullptr; @@ -1181,8 +1175,6 @@ void Engine::showTextureInImGuiWindow(std::string windowName, TextureBuffer* buf ImGui::End(); } -ImFontAtlas* Engine::getImGuiGlobalFontAtlas() { return globalFontAtlas; } - void Engine::preserveResourceUntilImguiFrameCompletes(std::shared_ptr texture) { resourcesPreservedForImGuiFrame.push_back(texture); } diff --git a/src/render/ground_plane.cpp b/src/render/ground_plane.cpp index dec2e205..d07935b2 100644 --- a/src/render/ground_plane.cpp +++ b/src/render/ground_plane.cpp @@ -277,7 +277,7 @@ void GroundPlane::draw(bool isRedraw) { render::engine->setDepthMode(DepthMode::Less); sceneAltFrameBuffer->resize(factor * view::bufferWidth / 2, factor * view::bufferHeight / 2); sceneAltFrameBuffer->setViewport(0, 0, factor * view::bufferWidth / 2, factor * view::bufferHeight / 2); - render::engine->setCurrentPixelScaling(factor / 2.); + render::engine->setCurrentPixelScaling(factor / 2. * options::uiScale); sceneAltFrameBuffer->bindForRendering(); sceneAltFrameBuffer->clearColor = {view::bgColor[0], view::bgColor[1], view::bgColor[2]}; @@ -411,7 +411,7 @@ void GroundPlane::buildGui() { ImGui::SetNextItemOpen(false, ImGuiCond_FirstUseEver); if (ImGui::TreeNode("Ground Plane")) { - ImGui::PushItemWidth(160); + ImGui::PushItemWidth(160*options::uiScale); if (ImGui::BeginCombo("Mode", modeName(options::groundPlaneMode).c_str())) { for (GroundPlaneMode m : {GroundPlaneMode::None, GroundPlaneMode::Tile, GroundPlaneMode::TileReflection, GroundPlaneMode::ShadowOnly}) { @@ -426,7 +426,7 @@ void GroundPlane::buildGui() { ImGui::PopItemWidth(); // Height - ImGui::PushItemWidth(80); + ImGui::PushItemWidth(80 * options::uiScale); switch (options::groundPlaneHeightMode) { case GroundPlaneHeightMode::Automatic: if (ImGui::SliderFloat("##HeightValue", options::groundPlaneHeightFactor.getValuePtr(), -1.0, 1.0)) @@ -446,7 +446,7 @@ void GroundPlane::buildGui() { } ImGui::PopItemWidth(); ImGui::SameLine(); - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100*options::uiScale); if (ImGui::BeginCombo("Height##Mode", heightModeName(options::groundPlaneHeightMode).c_str())) { for (GroundPlaneHeightMode m : {GroundPlaneHeightMode::Automatic, GroundPlaneHeightMode::Manual}) { std::string mName = heightModeName(m); diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index 5a52407b..e744a287 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -1562,6 +1562,9 @@ void MockGLEngine::initialize() { GLFrameBuffer* glScreenBuffer = new GLFrameBuffer(view::bufferWidth, view::bufferHeight, true); displayBuffer.reset(glScreenBuffer); + if (options::uiScale < 0) { // only set from system if the value is -1, meaning not set yet + options::uiScale = 1.; + } // normally we get initial values for the buffer size from the window framework, // with the mock backend we we need to manually set them to some sane value @@ -1578,6 +1581,19 @@ void MockGLEngine::initializeImGui() { configureImGui(); } +void MockGLEngine::configureImGui() { + + // don't both calling the style callbacks, there is no UI + + if (options::uiScale < 0) { + exception("uiScale is < 0. Perhaps it wasn't initialized?"); + } + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + io.Fonts->Build(); +} + void MockGLEngine::shutdown() { checkError(); shutdownImGui(); diff --git a/src/render/opengl/gl_engine_egl.cpp b/src/render/opengl/gl_engine_egl.cpp index 0d9d4db0..5d354798 100644 --- a/src/render/opengl/gl_engine_egl.cpp +++ b/src/render/opengl/gl_engine_egl.cpp @@ -254,6 +254,10 @@ void GLEngineEGL::initialize() { << "EGL version: " << majorVer << "." << minorVer << std::endl; } + if(options::uiScale < 0) { // only set from system if the value is -1, meaning not set yet + options::uiScale = 1.; + } + { // Manually create the screen frame buffer // NOTE: important difference here, we manually create both the framebuffer and and its render buffer, since // headless EGL means we are not getting them from a window @@ -432,6 +436,19 @@ void GLEngineEGL::initializeImGui() { configureImGui(); } +void GLEngineEGL::configureImGui() { + + // don't both calling the style callbacks, there is no UI + + if (options::uiScale < 0) { + exception("uiScale is < 0. Perhaps it wasn't initialized?"); + } + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + io.Fonts->Build(); +} + void GLEngineEGL::shutdown() { checkError(); shutdownImGui(); diff --git a/src/render/opengl/gl_engine_glfw.cpp b/src/render/opengl/gl_engine_glfw.cpp index d483c238..fb36312a 100644 --- a/src/render/opengl/gl_engine_glfw.cpp +++ b/src/render/opengl/gl_engine_glfw.cpp @@ -50,6 +50,15 @@ void GLEngineGLFW::initialize() { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + // This tells GLFW to scale window size/positioning/content based on the system-reported DPI scaling factor + // However, it can lead to some confusing behaviors, for instance, on linux with scaling=200%, if the user + // sets view::windowWidth = 1280, they might get back a window with windowWidth == bufferWidth == 2560, + // which is quite confusing. + // For this reason we _do not_ set this hint. If desired, the user can specify a windowWidth = 1280*uiScale, + // or let the window size by loaded from .polyscope.ini after setting manually once. + // glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + #if __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif @@ -72,6 +81,14 @@ void GLEngineGLFW::initialize() { setWindowResizable(view::windowResizable); + // Set the UI scale to account for system-requested DPI scaling + // Currently we do *not* watch for changes of this value e.g. if a window moves between + // monitors with different DPI behaviors. We could, but it would require some logic to + // avoid overwriting values that a user might have set. + if(options::uiScale < 0) { // only set from system if the value is -1, meaning not set yet + setUIScaleFromSystemDPI(); + } + // === Initialize openGL // Load openGL functions (using GLAD) #ifndef __APPLE__ @@ -101,6 +118,16 @@ void GLEngineGLFW::initialize() { populateDefaultShadersAndRules(); } +void GLEngineGLFW::setUIScaleFromSystemDPI() { + + float xScale, dont_use_yScale; + glfwGetWindowContentScale(mainWindow, &xScale, &dont_use_yScale); + + // clamp to values within [1x,4x] scaling + xScale = std::fmin(std::fmax(xScale, 1.0f), 4.0f); + + options::uiScale = xScale; +} void GLEngineGLFW::initializeImGui() { bindDisplay(); @@ -115,6 +142,34 @@ void GLEngineGLFW::initializeImGui() { configureImGui(); } +void GLEngineGLFW::configureImGui() { + + if(options::uiScale < 0){ + exception("uiScale is < 0. Perhaps it wasn't initialized?"); + } + + if (options::prepareImGuiFontsCallback) { + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + + // these are necessary if different fonts are loaded in the callback + // (don't totally understand why, allegedly it may change in the future) + ImGui_ImplOpenGL3_DestroyFontsTexture(); + + ImFontAtlas* _unused; + std::tie(_unused, regularFont, monoFont) = options::prepareImGuiFontsCallback(); + + ImGui_ImplOpenGL3_CreateFontsTexture(); + } + + + if (options::configureImGuiStyleCallback) { + options::configureImGuiStyleCallback(); + } +} + + void GLEngineGLFW::shutdown() { checkError(); @@ -132,6 +187,14 @@ void GLEngineGLFW::shutdownImGui() { } void GLEngineGLFW::ImGuiNewFrame() { + // TODO + // float xScale, dont_use_yScale; + // glfwGetWindowContentScale(mainWindow, &xScale, &dont_use_yScale); + // if (xScale == NULL) { + // xScale = 1.0f; + // } + // options::uiScale = std::min(std::max(xScale, 1.0f), 10.0f); + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index b16082f9..b6c1b314 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -1249,7 +1249,7 @@ void SurfaceMesh::buildCustomUI() { { // Flat shading or smooth shading? ImGui::SameLine(); - ImGui::PushItemWidth(85); + ImGui::PushItemWidth(85 * options::uiScale); auto styleName = [](const MeshShadeStyle& m) -> std::string { switch (m) { @@ -1278,7 +1278,7 @@ void SurfaceMesh::buildCustomUI() { { // Edge options ImGui::SameLine(); - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (edgeWidth.get() == 0.) { bool showEdges = false; if (ImGui::Checkbox("Edges", &showEdges)) { @@ -1291,14 +1291,14 @@ void SurfaceMesh::buildCustomUI() { } // Edge color - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (ImGui::ColorEdit3("Edge Color", &edgeColor.get()[0], ImGuiColorEditFlags_NoInputs)) setEdgeColor(edgeColor.get()); ImGui::PopItemWidth(); // Edge width ImGui::SameLine(); - ImGui::PushItemWidth(75); + ImGui::PushItemWidth(75 * options::uiScale); if (ImGui::SliderFloat("Width", &edgeWidth.get(), 0.001, 2.)) { // NOTE: this intentionally circumvents the setEdgeWidth() setter to avoid repopulating the buffer as the // slider is dragged---otherwise we repopulate the buffer on every change, which mostly works fine. This is a @@ -1419,12 +1419,12 @@ long long int SurfaceMesh::selectVertex() { ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Once); ImGui::Begin("Select vertex", &showWindow); - ImGui::PushItemWidth(300); + ImGui::PushItemWidth(300 * options::uiScale); ImGui::TextUnformatted("Hold ctrl and left-click to select a vertex"); ImGui::Separator(); // Choose by number - ImGui::PushItemWidth(300); + ImGui::PushItemWidth(300 * options::uiScale); static int iV = -1; ImGui::InputInt("index", &iV); if (ImGui::Button("Select by index")) { diff --git a/src/view.cpp b/src/view.cpp index 4511205e..f3f2940c 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -728,7 +728,7 @@ void buildViewGui() { std::string viewStyleName = to_string(view::style); - ImGui::PushItemWidth(120); + ImGui::PushItemWidth(120 * options::uiScale); std::array styles{NavigateStyle::Turntable, NavigateStyle::Free, NavigateStyle::Planar, NavigateStyle::None, NavigateStyle::FirstPerson}; if (ImGui::BeginCombo("##View Style", viewStyleName.c_str())) { @@ -747,7 +747,7 @@ void buildViewGui() { ImGui::Text("Camera Style"); { // == Up direction - ImGui::PushItemWidth(120); + ImGui::PushItemWidth(120 * options::uiScale); std::string upStyleName; switch (upDir) { case UpDir::XUp: @@ -802,7 +802,7 @@ void buildViewGui() { } { // == Front direction - ImGui::PushItemWidth(120); + ImGui::PushItemWidth(120 * options::uiScale); std::string frontStyleName; switch (frontDir) { case FrontDir::XFront: @@ -894,7 +894,7 @@ void buildViewGui() { ImGui::TextUnformatted("Bounding Box:"); - ImGui::PushItemWidth(200); + ImGui::PushItemWidth(200 * options::uiScale); glm::vec3& bboxMin = std::get<0>(state::boundingBox); glm::vec3& bboxMax = std::get<1>(state::boundingBox); if (ImGui::InputFloat3("min", &bboxMin[0])) updateStructureExtents(); @@ -958,7 +958,7 @@ void buildViewGui() { { ImGui::TextUnformatted("Dim:"); ImGui::SameLine(); - ImGui::PushItemWidth(50); + ImGui::PushItemWidth(50 * options::uiScale); bool changed = false; int currWidth = view::windowWidth; int currHeight = view::windowHeight; diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index 1ad3b668..299b5815 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -52,7 +52,7 @@ void VolumeGrid::buildCustomUI() { { // Edge options ImGui::SameLine(); - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (edgeWidth.get() == 0.) { bool showEdges = false; if (ImGui::Checkbox("Edges", &showEdges)) { @@ -65,14 +65,14 @@ void VolumeGrid::buildCustomUI() { } // Edge color - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (ImGui::ColorEdit3("Edge Color", &edgeColor.get()[0], ImGuiColorEditFlags_NoInputs)) setEdgeColor(edgeColor.get()); ImGui::PopItemWidth(); // Edge width ImGui::SameLine(); - ImGui::PushItemWidth(75); + ImGui::PushItemWidth(75 * options::uiScale); if (ImGui::SliderFloat("Width", &edgeWidth.get(), 0.001, 2.)) { // NOTE: this intentionally circumvents the setEdgeWidth() setter to avoid repopulating the buffer as the // slider is dragged---otherwise we repopulate the buffer on every change, which mostly works fine. This is a diff --git a/src/volume_grid_scalar_quantity.cpp b/src/volume_grid_scalar_quantity.cpp index cde441a9..43015569 100644 --- a/src/volume_grid_scalar_quantity.cpp +++ b/src/volume_grid_scalar_quantity.cpp @@ -70,7 +70,7 @@ void VolumeGridNodeScalarQuantity::buildCustomUI() { ImGui::SameLine(); // Set isovalue - ImGui::PushItemWidth(120); + ImGui::PushItemWidth(120 * options::uiScale); if (ImGui::SliderFloat("##Radius", &isosurfaceLevel.get(), vizRangeMin.get(), vizRangeMax.get(), "%.4e")) { // Note: we intentionally do this rather than calling setIsosurfaceLevel(), because that function immediately // recomputes the levelset mesh, which is too expensive during user interaction diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index 97f0e868..820540fd 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -871,7 +871,7 @@ void VolumeMesh::buildCustomUI() { ImGui::SameLine(); { // Edge options - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (edgeWidth.get() == 0.) { bool showEdges = false; if (ImGui::Checkbox("Edges", &showEdges)) { @@ -884,14 +884,14 @@ void VolumeMesh::buildCustomUI() { } // Edge color - ImGui::PushItemWidth(100); + ImGui::PushItemWidth(100 * options::uiScale); if (ImGui::ColorEdit3("Edge Color", &edgeColor.get()[0], ImGuiColorEditFlags_NoInputs)) setEdgeColor(edgeColor.get()); ImGui::PopItemWidth(); // Edge width ImGui::SameLine(); - ImGui::PushItemWidth(60); + ImGui::PushItemWidth(60 * options::uiScale); if (ImGui::SliderFloat("Width", &edgeWidth.get(), 0.001, 2.)) { // NOTE: this intentionally circumvents the setEdgeWidth() setter to avoid repopulating the buffer as the // slider is dragged---otherwise we repopulate the buffer on every change, which mostly works fine. This is a