Add ImGui overlay utility and PAUSE/realtime overlays to Studio.
PiperOrigin-RevId: 937834952 Change-Id: I3f3a0500a89b34e2e03b2fe88344b928edeafe8f
This commit is contained in:
committed by
Copybara-Service
parent
b6a0db7a17
commit
463b0fb27f
@@ -15,6 +15,7 @@
|
||||
#include "experimental/platform/ux/imgui_widgets.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
@@ -502,5 +503,118 @@ void DrawTextAt(const char* text, float x, float y, float z) {
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
}
|
||||
namespace {
|
||||
|
||||
float MeasureTextWidth(const char* text) {
|
||||
float max_width = 0.0f;
|
||||
const char* start = text;
|
||||
while (*start) {
|
||||
const char* end = start;
|
||||
while (*end && *end != '\n') ++end;
|
||||
max_width = std::max(max_width, ImGui::CalcTextSize(start, end).x);
|
||||
start = *end ? end + 1 : end;
|
||||
}
|
||||
return max_width;
|
||||
}
|
||||
|
||||
void SetNextWindowPosInside(OverlayPos pos, ImVec4 rect) {
|
||||
// compute anchor point and pivot from position enum
|
||||
ImVec2 anchor, pivot;
|
||||
float x = rect.x, y = rect.y, w = rect.z, h = rect.w;
|
||||
float scale = ImGui::GetWindowDpiScale();
|
||||
float margin = 8.0f * scale;
|
||||
switch (pos) {
|
||||
case OverlayPos::kTopLeft:
|
||||
anchor = {x + margin, y + margin};
|
||||
pivot = {0.0f, 0.0f};
|
||||
break;
|
||||
case OverlayPos::kTop:
|
||||
anchor = {x + w * 0.5f, y + margin};
|
||||
pivot = {0.5f, 0.0f};
|
||||
break;
|
||||
case OverlayPos::kTopRight:
|
||||
anchor = {x + w - margin, y + margin};
|
||||
pivot = {1.0f, 0.0f};
|
||||
break;
|
||||
case OverlayPos::kBottomLeft:
|
||||
anchor = {x + margin, y + h - margin};
|
||||
pivot = {0.0f, 1.0f};
|
||||
break;
|
||||
case OverlayPos::kBottom:
|
||||
anchor = {x + w * 0.5f, y + h - margin};
|
||||
pivot = {0.5f, 1.0f};
|
||||
break;
|
||||
case OverlayPos::kBottomRight:
|
||||
anchor = {x + w - margin, y + h - margin};
|
||||
pivot = {1.0f, 1.0f};
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowPos(anchor, ImGuiCond_Always, pivot);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool BeginOverlay(const char* id, OverlayPos pos, ImVec4 rect,
|
||||
float min_width, float alpha) {
|
||||
SetNextWindowPosInside(pos, rect);
|
||||
|
||||
if (min_width > 0.0f) {
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(min_width, -1.0f),
|
||||
ImVec2(FLT_MAX, -1.0f));
|
||||
}
|
||||
|
||||
constexpr ImGuiWindowFlags kFlags =
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
|
||||
return ImGui::Begin(id, nullptr, kFlags);
|
||||
}
|
||||
|
||||
void EndOverlay() {
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
void TextOverlay(const char* id, OverlayPos pos, ImVec4 workspace_rect,
|
||||
const char* text, ImVec4 color, float font_scale,
|
||||
float alpha, float min_width) {
|
||||
float scale = ImGui::GetWindowDpiScale();
|
||||
float padding = 30.0f * scale;
|
||||
float max_width = std::max(0.0f, workspace_rect.z - 20.0f);
|
||||
float text_width = MeasureTextWidth(text) * font_scale;
|
||||
float min_target = std::min(min_width * scale, max_width);
|
||||
float target = 0.0f;
|
||||
if (text_width + padding > max_width || min_width > 0.0f) {
|
||||
target = std::clamp(text_width + padding, min_target, max_width);
|
||||
}
|
||||
|
||||
if (BeginOverlay(id, pos, workspace_rect, target, alpha)) {
|
||||
if (font_scale != 1.0f) {
|
||||
ImGui::SetWindowFontScale(font_scale);
|
||||
}
|
||||
bool has_color = color.x != 0 || color.y != 0 || color.z != 0 ||
|
||||
color.w != 0;
|
||||
if (has_color) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
}
|
||||
if (text_width + padding > max_width) {
|
||||
ImGui::PushTextWrapPos(max_width - padding);
|
||||
ImGui::TextUnformatted(text);
|
||||
ImGui::PopTextWrapPos();
|
||||
} else {
|
||||
ImGui::TextUnformatted(text);
|
||||
}
|
||||
if (has_color) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
if (font_scale != 1.0f) {
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
}
|
||||
}
|
||||
EndOverlay();
|
||||
}
|
||||
|
||||
} // namespace mujoco::platform
|
||||
|
||||
@@ -692,6 +692,36 @@ ImPlotPairLayout ImPlot_ComputePairLayout();
|
||||
// [1,1,1]).
|
||||
void DrawTextAt(const char* text, float x, float y, float z);
|
||||
|
||||
// Grid position for overlays within a viewport rectangle.
|
||||
enum class OverlayPos {
|
||||
kTopLeft,
|
||||
kTop,
|
||||
kTopRight,
|
||||
kBottomLeft,
|
||||
kBottom,
|
||||
kBottomRight,
|
||||
};
|
||||
|
||||
// Begin an overlay window at the given grid position within a workspace rect.
|
||||
// workspace_rect: (x, y, width, height) of the viewport area.
|
||||
// min_width: minimum window width in scaled pixels (0 = auto).
|
||||
// alpha: window transparency (0.0-1.0).
|
||||
// Returns true if the overlay is visible (like ImGui::Begin).
|
||||
// Must be paired with EndOverlay(), even if this returns false.
|
||||
bool BeginOverlay(const char* id, OverlayPos pos, ImVec4 workspace_rect,
|
||||
float min_width = 0.0f, float alpha = 0.8f);
|
||||
void EndOverlay();
|
||||
|
||||
// One-liner: display a text overlay with automatic width sizing.
|
||||
// Measures the longest line in `text`, clamps to workspace width, wraps text
|
||||
// if needed.
|
||||
// color: text color. If all components are 0, uses the theme default.
|
||||
// font_scale: scale factor for the default font (1.0 = normal).
|
||||
void TextOverlay(const char* id, OverlayPos pos, ImVec4 workspace_rect,
|
||||
const char* text, ImVec4 color = ImVec4(0, 0, 0, 0),
|
||||
float font_scale = 1.0f, float alpha = 0.8f,
|
||||
float min_width = 0.0f);
|
||||
|
||||
} // namespace mujoco::platform
|
||||
|
||||
#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_UX_IMGUI_WIDGETS_H_
|
||||
|
||||
@@ -978,28 +978,43 @@ void App::BuildGui() {
|
||||
}
|
||||
|
||||
if (tmp_.info) {
|
||||
platform::ScopedStyle style;
|
||||
style.Var(ImGuiStyleVar_Alpha, 0.8f);
|
||||
const float scale = ImGui::GetWindowDpiScale();
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(workspace_rect.x,
|
||||
workspace_rect.y + workspace_rect.w),
|
||||
ImGuiCond_Always, ImVec2(0.0f, 1.0f));
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(240.0f * scale, -1.0f),
|
||||
ImVec2(FLT_MAX, -1.0f));
|
||||
const ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings;
|
||||
if (ImGui::Begin("Info", nullptr, flags)) {
|
||||
if (platform::BeginOverlay("Info", platform::OverlayPos::kBottomLeft,
|
||||
workspace_rect, 180.0f * scale)) {
|
||||
const float fps = renderer_->GetFps();
|
||||
platform::InfoGui(
|
||||
model(), data(),
|
||||
step_control_.GetPauseState() == PauseState::kNormalPaused, fps);
|
||||
}
|
||||
ImGui::End();
|
||||
platform::EndOverlay();
|
||||
}
|
||||
|
||||
// pause overlay
|
||||
if (has_model() &&
|
||||
step_control_.GetPauseState() == PauseState::kNormalPaused) {
|
||||
platform::TextOverlay("Pause", platform::OverlayPos::kTop,
|
||||
workspace_rect, "PAUSE", ImVec4(0, 0, 0, 0), 3.0f);
|
||||
}
|
||||
|
||||
// realtime factor overlay
|
||||
if (has_model() &&
|
||||
step_control_.GetPauseState() == PauseState::kUnpaused) {
|
||||
const float desired = step_control_.GetSpeed();
|
||||
const float measured = step_control_.GetSpeedMeasured();
|
||||
if (desired != 100.0f ||
|
||||
mju_abs(measured - desired) > 0.1f * desired) {
|
||||
char rtlabel[30];
|
||||
bool misaligned = mju_abs(measured - desired) > 0.1f * desired;
|
||||
if (misaligned) {
|
||||
std::snprintf(rtlabel, sizeof(rtlabel), "%g%% (%-4.1f%%)",
|
||||
desired, measured);
|
||||
} else {
|
||||
std::snprintf(rtlabel, sizeof(rtlabel), "%g%%", desired);
|
||||
}
|
||||
platform::TextOverlay("Realtime", platform::OverlayPos::kTopLeft,
|
||||
workspace_rect, rtlabel, ImVec4(0, 0, 0, 0),
|
||||
2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Display a drag-and-drop message if no model is loaded.
|
||||
|
||||
Reference in New Issue
Block a user