From cb0da74b414363f4cdb15c2897f8bb1202bc7e79 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sat, 5 Sep 2026 15:34:00 -0700 Subject: [PATCH] cabana: center speed dropdown label, add divider after loop button (#38785) --- openpilot/tools/cabana/ui/util.cc | 17 +++++---- openpilot/tools/cabana/ui/util.h | 2 +- .../tools/cabana/ui/widgets/videowidget.cc | 36 ++++++++++--------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/openpilot/tools/cabana/ui/util.cc b/openpilot/tools/cabana/ui/util.cc index bd1b2f8039..2e538e16f1 100644 --- a/openpilot/tools/cabana/ui/util.cc +++ b/openpilot/tools/cabana/ui/util.cc @@ -442,18 +442,21 @@ bool menuButton(const char *id, const std::string &text, const char *popup_id, b // on press; a press while it is open toggles it closed (imgui closes the popup at the end of the frame of // a click outside it, so only open when it is not already open) if (bold) pushBoldFont(); - ImGui::PushStyleColor(ImGuiCol_Button, popup_open ? style.Colors[ImGuiCol_ButtonActive] : ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); - const bool clicked = ImGui::ButtonEx((text + "###" + id).c_str(), ImVec2(width, 0.0f), ImGuiButtonFlags_PressedOnClick); - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(); const float text_width = ImGui::CalcTextSize(text.c_str(), nullptr, true).x; const float ascent = ImGui::GetFontBaked()->Ascent; + // the text and the arrow are centered as a group in the button + const float padding_x = std::max(style.FramePadding.x, (width - (text_width + MENU_ARROW_SPACING + MENU_ARROW_SIZE)) * 0.5f); + ImGui::PushStyleColor(ImGuiCol_Button, popup_open ? style.Colors[ImGuiCol_ButtonActive] : ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(padding_x, style.FramePadding.y)); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); + const bool clicked = ImGui::ButtonEx((text + "###" + id).c_str(), ImVec2(width, 0.0f), ImGuiButtonFlags_PressedOnClick); + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(); if (bold) popBoldFont(); // a 6 px arrow right after the text, sitting on the text baseline const ImVec2 min = ImGui::GetItemRectMin(); - const float x = min.x + style.FramePadding.x + text_width + MENU_ARROW_SPACING; + const float x = min.x + padding_x + text_width + MENU_ARROW_SPACING; const float baseline = min.y + style.FramePadding.y + ascent; ImGui::GetWindowDrawList()->AddTriangleFilled(ImVec2(x, baseline - MENU_ARROW_SIZE * 0.5f), ImVec2(x + MENU_ARROW_SIZE, baseline - MENU_ARROW_SIZE * 0.5f), diff --git a/openpilot/tools/cabana/ui/util.h b/openpilot/tools/cabana/ui/util.h index 5739cfa3bc..d180c073b7 100644 --- a/openpilot/tools/cabana/ui/util.h +++ b/openpilot/tools/cabana/ui/util.h @@ -182,7 +182,7 @@ float toolbarWidth(const std::vector &items, size_t spacer_index); void drawToolbar(const std::vector &items, size_t spacer_index); // an auto-raise button that opens `popup_id` below itself, with a dropdown arrow after the text. width 0: -// sized to the text, otherwise the arrow sits at the right edge +// sized to the text, otherwise the text and the arrow are centered in the button float menuButtonWidth(const std::string &text, bool bold = false); bool menuButton(const char *id, const std::string &text, const char *popup_id, bool bold = false, float width = 0.0f); diff --git a/openpilot/tools/cabana/ui/widgets/videowidget.cc b/openpilot/tools/cabana/ui/widgets/videowidget.cc index ed5683f74b..9a54351ab6 100644 --- a/openpilot/tools/cabana/ui/widgets/videowidget.cc +++ b/openpilot/tools/cabana/ui/widgets/videowidget.cc @@ -143,7 +143,7 @@ static float toolbarHeight() { return TOOLBAR_MARGIN_Y + ImGui::GetFrameHeight() void VideoWidget::drawPlaybackController() { beginToolbar(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + TOOLBAR_MARGIN_Y); - const float speed_width = menuButtonWidth("0.05x ", true); + const float speed_width = menuButtonWidth("0.05x", true); const char *play_icon = can->isPaused() ? icon::PLAY : icon::PAUSE; const char *play_tooltip = can->isPaused() ? "Play" : "Pause"; @@ -186,21 +186,25 @@ void VideoWidget::drawPlaybackController() { } // the expanding spacer: the items after it are right aligned as long as everything fits const size_t spacer_index = items.size(); - if (!can->liveStreaming()) { - items.push_back({toolbarButtonWidth(loop_icon), [&]() { if (toolButton("loop", loop_icon, "Loop playback")) loopPlaybackClicked(); }, - "Loop playback", [this]() { loopPlaybackClicked(); }}); - } - items.push_back({speed_width, [&]() { drawSpeedDropdown(speed_width); }}); - if (!can->liveStreaming()) { - ToolbarItem separator{TOOLBAR_SEPARATOR_EXTENT, []() { + auto separator = []() { + ToolbarItem item{TOOLBAR_SEPARATOR_EXTENT, []() { // a 1 px separator line centered in TOOLBAR_SEPARATOR_EXTENT, inset from the top and bottom const ImVec2 min = ImGui::GetCursorScreenPos(); ImGui::Dummy(ImVec2(TOOLBAR_SEPARATOR_EXTENT, ImGui::GetFrameHeight())); const float x = std::floor(min.x + TOOLBAR_SEPARATOR_EXTENT * 0.5f); ImGui::GetWindowDrawList()->AddLine(ImVec2(x, min.y + 4.0f), ImVec2(x, min.y + ImGui::GetFrameHeight() - 4.0f), ImGui::GetColorU32(ImGuiCol_Separator)); }}; - separator.in_menu = false; - items.push_back(std::move(separator)); + item.in_menu = false; + return item; + }; + if (!can->liveStreaming()) { + items.push_back({toolbarButtonWidth(loop_icon), [&]() { if (toolButton("loop", loop_icon, "Loop playback")) loopPlaybackClicked(); }, + "Loop playback", [this]() { loopPlaybackClicked(); }}); + items.push_back(separator()); + } + items.push_back({speed_width, [&]() { drawSpeedDropdown(speed_width); }}); + if (!can->liveStreaming()) { + items.push_back(separator()); items.push_back({toolbarButtonWidth(icon::INFO_CIRCLE), [&]() { if (toolButton("route_info", icon::INFO_CIRCLE, "View route details")) showRouteInfo(); }, "View route details", [this]() { showRouteInfo(); }}); @@ -221,16 +225,16 @@ void VideoWidget::toggleTimeDisplay() { settings.absolute_time = !settings.absolute_time; } -static std::string speedText(float speed, const char *suffix) { +static std::string speedText(float speed) { char buf[32]; - snprintf(buf, sizeof(buf), "%gx%s", speed, suffix); + snprintf(buf, sizeof(buf), "%gx", speed); return buf; } void VideoWidget::createSpeedDropdown() { speed_index_ = NORMAL_SPEED_INDEX; can->setSpeed(speeds[speed_index_]); - speed_text_ = speedText(speeds[speed_index_], " "); + speed_text_ = speedText(speeds[speed_index_]); } void VideoWidget::drawSpeedDropdown(float width) { @@ -247,14 +251,14 @@ void VideoWidget::drawSpeedMenuItems() { const float indent = ImGui::GetFontSize(); float label_width = 0; for (int i = 0; i < (int)std::size(speeds); ++i) { - label_width = std::max(label_width, ImGui::CalcTextSize(speedText(speeds[i], "").c_str()).x); + label_width = std::max(label_width, ImGui::CalcTextSize(speedText(speeds[i]).c_str()).x); } for (int i = 0; i < (int)std::size(speeds); ++i) { const float speed = speeds[i]; - if (radioMenuItem(speedText(speed, "").c_str(), speed_index_ == i, indent + label_width + indent)) { + if (radioMenuItem(speedText(speed).c_str(), speed_index_ == i, indent + label_width + indent)) { speed_index_ = i; can->setSpeed(speed); - speed_text_ = speedText(speed, " "); + speed_text_ = speedText(speed); } } }