Studio: toolbar cosmetic improvements
- Remove hard-coded button sizes (48×32) from toolbar icons; buttons now use default ImGui sizing, matching the existing copy-camera button. - Remove the Unload button (available via menu). - Add ImGui_ColorButtonEx widget with per-corner rounding via ImDrawFlags, enabling segmented button-group visuals. - Use segmented rounding for the pause/viscous/play triplet: rounded-left, square, rounded-right. - Make pause and play buttons 40% wider than the viscous button for emphasis. - Replace the theme combo dropdown with a cycling button. - Reduce toolbar height from 48px to 34px and vertically center content via WindowPadding. - Zero out table CellPadding in the toolbar for precise vertical centering. - Remove the speed combo's inflated vertical padding. PiperOrigin-RevId: 906853958 Change-Id: I6e4ec7243adb9e0f1f06f8779fa896a6865c46c7
This commit is contained in:
committed by
Copybara-Service
parent
25a9114705
commit
6f275b79c6
@@ -97,15 +97,15 @@ void SetupTheme(GuiTheme theme) {
|
||||
c[ImGuiCol_TextSelectedBg] = ImVec4(0.73, 0.73, 0.73, 0.35);
|
||||
c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80, 0.80, 0.80, 0.35);
|
||||
c[ImGuiCol_DragDropTarget] = ImVec4(1.00, 1.00, 0.00, 0.90);
|
||||
c[ImGuiCol_NavHighlight] = ImVec4(0.26, 0.59, 0.98, 1.00);
|
||||
c[ImGuiCol_NavCursor] = ImVec4(0.26, 0.59, 0.98, 1.00);
|
||||
c[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00, 1.00, 1.00, 0.70);
|
||||
c[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80, 0.80, 0.80, 0.20);
|
||||
c[ImGuiCol_DockingEmptyBg] = ImVec4(0.38, 0.38, 0.38, 1.00);
|
||||
c[ImGuiCol_Tab] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_TabHovered] = ImVec4(0.40, 0.40, 0.40, 1.00);
|
||||
c[ImGuiCol_TabActive] = ImVec4(0.33, 0.33, 0.33, 1.00);
|
||||
c[ImGuiCol_TabUnfocused] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_TabUnfocusedActive] = ImVec4(0.33, 0.33, 0.33, 1.00);
|
||||
c[ImGuiCol_TabSelected] = ImVec4(0.33, 0.33, 0.33, 1.00);
|
||||
c[ImGuiCol_TabDimmed] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_TabDimmedSelected] = ImVec4(0.33, 0.33, 0.33, 1.00);
|
||||
c[ImGuiCol_DockingPreview] = ImVec4(0.85, 0.85, 0.85, 0.28);
|
||||
c[ImGuiCol_WindowBg].w = 1.0f;
|
||||
} else if (theme == GuiTheme::kLight) {
|
||||
@@ -220,7 +220,7 @@ ImVec4 ConfigureDockingLayout() {
|
||||
const float kOptionsRelWidth = 0.22f;
|
||||
const float kInspectorRelWidth = 0.22f;
|
||||
const float kStatsRelHeight = 0.3f;
|
||||
const float kToolsBarHeight = 48.f * scale;
|
||||
const float kToolsBarHeight = 36.f * scale;
|
||||
const float kStatusBarHeight = 32.f * scale;
|
||||
|
||||
const ImVec2 dockspace_pos{viewport->WorkPos.x,
|
||||
@@ -303,6 +303,9 @@ ImVec4 ConfigureDockingLayout() {
|
||||
platform::ScopedStyle style;
|
||||
style.Var(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||
style.Var(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
const float toolbar_vpad =
|
||||
std::max(0.f, (kToolsBarHeight - ImGui::GetFrameHeight()) * 0.5f);
|
||||
style.Var(ImGuiStyleVar_WindowPadding, ImVec2(4, toolbar_vpad));
|
||||
ImGui::SetNextWindowPos(viewport->WorkPos, ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kToolsBarHeight),
|
||||
ImGuiCond_Always);
|
||||
@@ -335,18 +338,23 @@ ImVec4 ConfigureDockingLayout() {
|
||||
void StepControlGui(const mjModel* model, StepControl* step_control,
|
||||
int& speed_index) {
|
||||
platform::ScopedStyle style;
|
||||
style.Var(ImGuiStyleVar_FrameRounding, 2.f);
|
||||
style.Var(ImGuiStyleVar_FrameRounding, 8.f);
|
||||
|
||||
const ImColor yellow(255, 215, 0, 255);
|
||||
const ImColor green(40, 180, 40, 255);
|
||||
const float scale = ImGui::GetWindowDpiScale();
|
||||
ImVec2 button_size(48.f * scale, 32.f * scale);
|
||||
|
||||
auto make_button = [&](const char* icon, StepControl::PauseState target_state,
|
||||
ImColor color, const char* tooltip = "",
|
||||
float hover_alpha = 1.f) {
|
||||
ImColor color, ImDrawFlags corners,
|
||||
const char* tooltip = "",
|
||||
float hover_alpha = 1.f, float width_scale = 1.f) {
|
||||
ImVec2 size(0, 0);
|
||||
if (width_scale != 1.f) {
|
||||
const ImGuiStyle& s = ImGui::GetStyle();
|
||||
const float w = ImGui::CalcTextSize(icon).x + s.FramePadding.x * 2;
|
||||
size.x = w * width_scale;
|
||||
}
|
||||
bool active = step_control->GetPauseState() == target_state;
|
||||
if (ImGui_ColorButton(icon, active, color, button_size, hover_alpha)) {
|
||||
if (ImGui_ColorButtonEx(icon, active, color, corners, size, hover_alpha)) {
|
||||
step_control->SetPauseState(target_state);
|
||||
}
|
||||
if (!std::string_view(tooltip).empty()) {
|
||||
@@ -355,26 +363,28 @@ void StepControlGui(const mjModel* model, StepControl* step_control,
|
||||
};
|
||||
|
||||
make_button(ICON_FA_PAUSE, StepControl::PauseState::kNormalPaused, yellow,
|
||||
"Pause");
|
||||
ImDrawFlags_RoundCornersLeft, "Pause", .3f, 1.6f);
|
||||
ImGui::SameLine(0.f, 0.f);
|
||||
make_button(ICON_FA_MAGIC, StepControl::PauseState::kViscousPaused, yellow,
|
||||
"Viscous Pause");
|
||||
ImDrawFlags_RoundCornersNone, "Viscous Pause", .3f, 1.3f);
|
||||
ImGui::SameLine(0.f, 0.f);
|
||||
make_button(ICON_FA_PLAY, StepControl::PauseState::kUnpaused, green, "", .6f);
|
||||
make_button(ICON_FA_PLAY, StepControl::PauseState::kUnpaused, green,
|
||||
ImDrawFlags_RoundCornersRight, "", .3f, 1.6f);
|
||||
|
||||
// Speed selection.
|
||||
ImGui::SameLine();
|
||||
const float pad_y = (button_size.y - ImGui::GetFontSize()) * .5f;
|
||||
style.Reset();
|
||||
ImGui::SameLine(0, ImGui::GetFrameHeight() * .6f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
|
||||
ImVec2(ImGui::GetStyle().FramePadding.x + 5.f, pad_y));
|
||||
ImVec2(ImGui::GetStyle().FramePadding.x + 5.f,
|
||||
ImGui::GetStyle().FramePadding.y));
|
||||
|
||||
const auto [misaligned, measured] = IsSpeedMisaligned(*step_control);
|
||||
char speed_preview[64];
|
||||
if (misaligned) {
|
||||
snprintf(speed_preview, sizeof(speed_preview), "%s%s (%-4.1f%%)",
|
||||
snprintf(speed_preview, sizeof(speed_preview), "%s %s (%-4.1f%%)",
|
||||
ICON_FA_TACHOMETER, kPercentRealTime[speed_index], measured);
|
||||
} else {
|
||||
snprintf(speed_preview, sizeof(speed_preview), "%s%s", ICON_FA_TACHOMETER,
|
||||
snprintf(speed_preview, sizeof(speed_preview), "%s %s", ICON_FA_TACHOMETER,
|
||||
kPercentRealTime[speed_index]);
|
||||
}
|
||||
|
||||
@@ -400,39 +410,22 @@ void StepControlGui(const mjModel* model, StepControl* step_control,
|
||||
}
|
||||
}
|
||||
|
||||
bool ThemeSelectGui(GuiTheme* theme) {
|
||||
bool ThemeSelectGui(GuiTheme* theme, const ImVec2& size) {
|
||||
static constexpr const char* ICON_DARKMODE = ICON_FA_CIRCLE;
|
||||
static constexpr const char* ICON_LIGHTMODE = ICON_FA_CIRCLE_O;
|
||||
static constexpr const char* ICON_CLASSICMODE = ICON_FA_ADJUST;
|
||||
const char* theme_icons[] = {ICON_LIGHTMODE, ICON_DARKMODE, ICON_CLASSICMODE};
|
||||
const char* theme_tooltips[] = {"Light Mode", "Dark Mode", "Classic Mode"};
|
||||
const GuiTheme theme_values[] = {
|
||||
GuiTheme::kLight,
|
||||
GuiTheme::kDark,
|
||||
GuiTheme::kClassic,
|
||||
};
|
||||
|
||||
bool changed = false;
|
||||
|
||||
int theme_idx = static_cast<int>(*theme);
|
||||
ImGui::SetNextItemWidth(ImGui::CalcTextSize(theme_icons[0]).x +
|
||||
ImGui::GetStyle().FramePadding.x * 2);
|
||||
if (ImGui::BeginCombo("##Theme", theme_icons[theme_idx],
|
||||
ImGuiComboFlags_NoArrowButton)) {
|
||||
for (int n = 0; n < IM_ARRAYSIZE(theme_icons); n++) {
|
||||
if (ImGui::Selectable(theme_icons[n], (theme_idx == n))) {
|
||||
*theme = theme_values[n];
|
||||
changed = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", theme_tooltips[n]);
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
if (ImGui::Button(theme_icons[theme_idx], size)) {
|
||||
theme_idx = (theme_idx + 1) % IM_ARRAYSIZE(theme_icons);
|
||||
*theme = static_cast<GuiTheme>(theme_idx);
|
||||
return true;
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Theme");
|
||||
ImGui::SetItemTooltip("%s", theme_tooltips[theme_idx]);
|
||||
|
||||
return changed;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LabelSelectionGui(mjvOption* opts) {
|
||||
|
||||
@@ -81,7 +81,7 @@ void StepControlGui(const mjModel* model, StepControl* step_control,
|
||||
int& speed_index);
|
||||
|
||||
// UX for selecting the GUI theme.
|
||||
bool ThemeSelectGui(GuiTheme* theme);
|
||||
bool ThemeSelectGui(GuiTheme* theme, const ImVec2& size = ImVec2(0, 0));
|
||||
|
||||
// UX for selecting the visualization label option.
|
||||
bool LabelSelectionGui(mjvOption* opts);
|
||||
|
||||
@@ -375,9 +375,7 @@ void ImGui_EndHSplit(bool open) {
|
||||
}
|
||||
|
||||
void MaybeSaveToClipboard(const std::string& contents) {
|
||||
if (ImGui::GetIO().SetClipboardTextFn) {
|
||||
ImGui::GetIO().SetClipboardTextFn(nullptr, contents.c_str());
|
||||
}
|
||||
ImGui::SetClipboardText(contents.c_str());
|
||||
}
|
||||
|
||||
ImPlotFlags ImPlot_SetupPlotFlags(ImVec2 plot_size) {
|
||||
|
||||
@@ -551,6 +551,55 @@ inline bool ImGui_ColorButton(const char* label, bool active, ImColor color,
|
||||
return ImGui::Button(label, size);
|
||||
}
|
||||
|
||||
// Like ImGui_ColorButton, but with per-corner rounding control via ImDrawFlags.
|
||||
// Use ImDrawFlags_RoundCornersLeft, ImDrawFlags_RoundCornersRight,
|
||||
// ImDrawFlags_RoundCornersNone, ImDrawFlags_RoundCornersAll, etc.
|
||||
inline bool ImGui_ColorButtonEx(const char* label, bool active, ImColor color,
|
||||
ImDrawFlags corners,
|
||||
const ImVec2& size = ImVec2(0, 0),
|
||||
float hover_alpha = 0.5f) {
|
||||
const ImGuiStyle& s = ImGui::GetStyle();
|
||||
const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);
|
||||
const ImVec2 btn_size(
|
||||
size.x > 0 ? size.x : label_size.x + s.FramePadding.x * 2,
|
||||
size.y > 0 ? size.y : label_size.y + s.FramePadding.y * 2);
|
||||
|
||||
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
ImGui::InvisibleButton(label, btn_size);
|
||||
const bool clicked = ImGui::IsItemClicked();
|
||||
const bool hovered = ImGui::IsItemHovered();
|
||||
|
||||
// Determine background color.
|
||||
const ImColor hover_color(color.Value.x, color.Value.y, color.Value.z,
|
||||
color.Value.w * hover_alpha);
|
||||
ImColor bg;
|
||||
if (active) {
|
||||
bg = color;
|
||||
} else if (hovered) {
|
||||
bg = hover_color;
|
||||
} else {
|
||||
bg = ImGui::GetColorU32(ImGuiCol_Button);
|
||||
}
|
||||
|
||||
// Draw background with per-corner rounding.
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
const ImVec2 max(pos.x + btn_size.x, pos.y + btn_size.y);
|
||||
dl->AddRectFilled(pos, max, bg, s.FrameRounding, corners);
|
||||
|
||||
// Draw border.
|
||||
if (s.FrameBorderSize > 0) {
|
||||
dl->AddRect(pos, max, ImGui::GetColorU32(ImGuiCol_Border),
|
||||
s.FrameRounding, corners, s.FrameBorderSize);
|
||||
}
|
||||
|
||||
// Draw label centered.
|
||||
const ImVec2 text_pos(pos.x + (btn_size.x - label_size.x) * 0.5f,
|
||||
pos.y + (btn_size.y - label_size.y) * 0.5f);
|
||||
dl->AddText(text_pos, ImGui::GetColorU32(ImGuiCol_Text), label);
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
// Begin a boxed section with outer borders - use EndBoxSection to close.
|
||||
inline bool BeginBoxSection(const char* id, ImGuiTableFlags extra_flags = 0) {
|
||||
ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | extra_flags;
|
||||
|
||||
@@ -72,13 +72,11 @@ static void SelectParentPerturb(const mjModel* model, mjvPerturb& perturb) {
|
||||
}
|
||||
|
||||
static constexpr const char* ICON_COPY_CAMERA = platform::ICON_FA_COPY;
|
||||
static constexpr const char* ICON_UNLOAD_MODEL = platform::ICON_FA_EJECT;
|
||||
static constexpr const char* ICON_RELOAD_MODEL = platform::ICON_FA_REFRESH;
|
||||
static constexpr const char* ICON_RESET_MODEL = platform::ICON_FA_UNDO;
|
||||
static constexpr const char* ICON_PREV_FRAME = platform::ICON_FA_CARET_LEFT;
|
||||
static constexpr const char* ICON_NEXT_FRAME = platform::ICON_FA_CARET_RIGHT;
|
||||
static constexpr const char* ICON_CURR_FRAME = platform::ICON_FA_FAST_FORWARD;
|
||||
static constexpr const char* ICON_RELOAD_SPEC = platform::ICON_FA_REFRESH;
|
||||
static constexpr const char* ICON_UNDO_SPEC = platform::ICON_FA_UNDO;
|
||||
static constexpr const char* ICON_REDO_SPEC = platform::ICON_FA_REPEAT;
|
||||
|
||||
@@ -1402,13 +1400,12 @@ void App::HelpGui() {
|
||||
}
|
||||
|
||||
void App::ToolBarGui() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0));
|
||||
if (ImGui::BeginTable("##ToolBarTable", 2)) {
|
||||
platform::ScopedStyle style;
|
||||
const ImColor red(220, 40, 40, 255);
|
||||
|
||||
const float scale = ImGui::GetWindowDpiScale();
|
||||
const ImVec2 button_size(48.f * scale, 32.f * scale);
|
||||
const ImVec2 play_button_size(80.f * scale, 32.f * scale);
|
||||
style.Var(ImGuiStyleVar_ItemSpacing,
|
||||
ImVec2(ImGui::GetStyle().ItemSpacing.x * 2.0f,
|
||||
ImGui::GetStyle().ItemSpacing.y));
|
||||
|
||||
const float label_width = GetExpectedLabelWidth();
|
||||
const float copy_btn_width = ImGui::CalcTextSize(ICON_COPY_CAMERA).x +
|
||||
@@ -1420,42 +1417,29 @@ void App::ToolBarGui() {
|
||||
const float right_width = label_width + sp + label_width + sp +
|
||||
label_width + sp + copy_btn_width + sp +
|
||||
theme_width;
|
||||
const float separator_width = .2f * button_size.x;
|
||||
const float separator_width = ImGui::GetFrameHeight() * .6f;
|
||||
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, right_width);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetStyle().WindowPadding.x);
|
||||
|
||||
// Combined (Unload, Reload) widget
|
||||
const float btn_size = ImGui::GetFrameHeight();
|
||||
const ImVec2 square_size(btn_size, btn_size);
|
||||
|
||||
// Reload button.
|
||||
{
|
||||
style.Var(ImGuiStyleVar_FrameRounding, 2.0f);
|
||||
|
||||
// Unload button.
|
||||
{
|
||||
const ImColor a = red;
|
||||
const ImColor h(a.Value.x, a.Value.y, a.Value.z, a.Value.w * 0.6f);
|
||||
style.Color(ImGuiCol_ButtonHovered, h);
|
||||
style.Color(ImGuiCol_ButtonActive, a);
|
||||
|
||||
if (ImGui::Button(ICON_UNLOAD_MODEL, button_size)) {
|
||||
InitEmptyModel();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Unload");
|
||||
style.Reset();
|
||||
}
|
||||
|
||||
// Reload button.
|
||||
ImGui::SameLine(0, 0);
|
||||
if (ImGui::Button(ICON_RELOAD_MODEL, button_size)) {
|
||||
if (ImGui::Button(ICON_RELOAD_MODEL, square_size)) {
|
||||
RequestModelReload();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Reload");
|
||||
}
|
||||
|
||||
// Reset button.
|
||||
ImGui::SameLine(0, separator_width);
|
||||
if (ImGui::Button(ICON_RESET_MODEL, button_size)) {
|
||||
ImGui::SameLine(0, 0.5 * separator_width);
|
||||
if (ImGui::Button(ICON_RESET_MODEL, square_size)) {
|
||||
ResetPhysics();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Reset");
|
||||
@@ -1465,10 +1449,8 @@ void App::ToolBarGui() {
|
||||
platform::StepControlGui(model(), &step_control_, tmp_.speed_index);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() +
|
||||
(button_size.y - ImGui::GetFrameHeight()) * 0.5f);
|
||||
|
||||
if (ImGui::Button(ICON_COPY_CAMERA)) {
|
||||
if (ImGui::Button(ICON_COPY_CAMERA, square_size)) {
|
||||
std::string camera_string = platform::CameraToString(data(), &camera_);
|
||||
platform::MaybeSaveToClipboard(camera_string);
|
||||
}
|
||||
@@ -1488,13 +1470,14 @@ void App::ToolBarGui() {
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(GetExpectedLabelWidth());
|
||||
if (platform::ThemeSelectGui(&ui_.theme)) {
|
||||
if (platform::ThemeSelectGui(&ui_.theme, square_size)) {
|
||||
platform::SetupTheme(ui_.theme);
|
||||
ImGui::GetIO().WantSaveIniSettings = true;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
void App::StatusBarGui() {
|
||||
|
||||
Reference in New Issue
Block a user