From 9796345888533747d755a39b0def8face2a0b958 Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Wed, 29 Apr 2026 03:41:21 -0700 Subject: [PATCH] Add font scaling to studio, bound to CTRL+MINUS/PLUS PiperOrigin-RevId: 907491215 Change-Id: I144467c3ff0e3d11f4a5bfb6ed2b1e4e88b8693d --- src/experimental/platform/ux/gui.cc | 23 +++++++++++++++++++++-- src/experimental/platform/ux/gui.h | 3 +++ src/experimental/studio/app.cc | 12 ++++++++++++ src/experimental/studio/app.h | 1 + 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/experimental/platform/ux/gui.cc b/src/experimental/platform/ux/gui.cc index ccf1ed91..d7a3946c 100644 --- a/src/experimental/platform/ux/gui.cc +++ b/src/experimental/platform/ux/gui.cc @@ -213,15 +213,32 @@ void SetupTheme(GuiTheme theme) { s.DockingNodeHasCloseButton = false; } +void RescaleDock(float ratio) { + if (ratio == 1) return; + ImGuiID root = ImGui::GetID("Root"); + ImGuiDockNode* root_node = ImGui::DockBuilderGetNode(root); + if (root_node) { + struct ScaleNodes { + static void Apply(ImGuiDockNode* node, float r) { + node->SizeRef.x *= r; + if (node->ChildNodes[0]) Apply(node->ChildNodes[0], r); + if (node->ChildNodes[1]) Apply(node->ChildNodes[1], r); + } + }; + ScaleNodes::Apply(root_node, ratio); + } +} + ImVec4 ConfigureDockingLayout() { ImGuiViewport* viewport = ImGui::GetMainViewport(); const float scale = ImGui::GetWindowDpiScale(); + const float font_scale = ImGui::GetIO().FontGlobalScale; const float kOptionsRelWidth = 0.22f; const float kInspectorRelWidth = 0.22f; const float kStatsRelHeight = 0.3f; - const float kToolsBarHeight = 36.f * scale; - const float kStatusBarHeight = 32.f * scale; + const float kToolsBarHeight = 36.f * scale * font_scale; + const float kStatusBarHeight = 32.f * scale * font_scale; const ImVec2 dockspace_pos{viewport->WorkPos.x, viewport->WorkPos.y + kToolsBarHeight}; @@ -303,6 +320,7 @@ ImVec4 ConfigureDockingLayout() { platform::ScopedStyle style; style.Var(ImGuiStyleVar_WindowBorderSize, 1.0f); style.Var(ImGuiStyleVar_WindowRounding, 0.0f); + style.Var(ImGuiStyleVar_WindowMinSize, ImVec2(1, 1)); const float toolbar_vpad = std::max(0.f, (kToolsBarHeight - ImGui::GetFrameHeight()) * 0.5f); style.Var(ImGuiStyleVar_WindowPadding, ImVec2(4, toolbar_vpad)); @@ -318,6 +336,7 @@ ImVec4 ConfigureDockingLayout() { platform::ScopedStyle style; style.Var(ImGuiStyleVar_WindowBorderSize, 1.0f); style.Var(ImGuiStyleVar_WindowRounding, 0.0f); + style.Var(ImGuiStyleVar_WindowMinSize, ImVec2(1, 1)); ImGui::SetNextWindowPos(ImVec2(0, viewport->Size.y - kStatusBarHeight), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kStatusBarHeight), diff --git a/src/experimental/platform/ux/gui.h b/src/experimental/platform/ux/gui.h index e9b23dfc..cc2ae08f 100644 --- a/src/experimental/platform/ux/gui.h +++ b/src/experimental/platform/ux/gui.h @@ -42,6 +42,9 @@ enum class GuiTheme { // Updates the ImGui internal style state to match the requested theme. void SetupTheme(GuiTheme theme); +// Rescales all dock node widths by the given ratio. +void RescaleDock(float ratio); + // Configures the ImGui docking module to the standard layout used by Studio. // This includes the following named sections: // "ToolBar": fixed size bar spanning the top of the window; for placing diff --git a/src/experimental/studio/app.cc b/src/experimental/studio/app.cc index 25dd1676..576462a5 100644 --- a/src/experimental/studio/app.cc +++ b/src/experimental/studio/app.cc @@ -578,6 +578,14 @@ void App::HandleKeyboardEvents() { tmp_.inspector_panel = !tmp_.inspector_panel; } else if (ImGui_IsChordJustPressed(ImGuiKey_Tab)) { tmp_.options_panel = !tmp_.options_panel; + } else if (ImGui_IsChordJustPressed(ImGuiKey_Minus | ImGuiMod_Ctrl)) { + float old_scale = ui_.font_scale; + ui_.font_scale = std::clamp(ui_.font_scale - 0.1f, 0.5f, 3.0f); + platform::RescaleDock(ui_.font_scale / old_scale); + } else if (ImGui_IsChordJustPressed(ImGuiKey_Equal | ImGuiMod_Ctrl)) { + float old_scale = ui_.font_scale; + ui_.font_scale = std::clamp(ui_.font_scale + 0.1f, 0.5f, 3.0f); + platform::RescaleDock(ui_.font_scale / old_scale); } else if (ImGui_IsChordJustPressed(ImGuiKey_Minus)) { SetSpeedIndex(tmp_.speed_index + 1); } else if (ImGui_IsChordJustPressed(ImGuiKey_Equal)) { @@ -823,6 +831,8 @@ void App::BuildGui() { platform::SetupTheme(ui_.theme); } + ImGui::GetIO().FontGlobalScale = ui_.font_scale; + const ImVec4 workspace_rect = platform::ConfigureDockingLayout(); // Place charts in bottom right corner of the workspace. @@ -1841,11 +1851,13 @@ float App::GetExpectedLabelWidth() { App::UiState::Dict App::UiState::ToDict() const { return { {"theme", std::to_string(static_cast(theme))}, + {"font_scale", std::to_string(font_scale)}, }; } void App::UiState::FromDict(const Dict& dict) { *this = UiState(); theme = ReadIniValue(dict, "theme", theme); + font_scale = platform::ReadIniValue(dict, "font_scale", font_scale); } } // namespace mujoco::studio diff --git a/src/experimental/studio/app.h b/src/experimental/studio/app.h index 2a1c7326..bef5f6ed 100644 --- a/src/experimental/studio/app.h +++ b/src/experimental/studio/app.h @@ -105,6 +105,7 @@ class App { int camera_idx = platform::kTumbleCameraIdx; int key_idx = 0; platform::GuiTheme theme = platform::GuiTheme::kLight; + float font_scale = 1.0f; using Dict = std::unordered_map; Dict ToDict() const;