diff --git a/openpilot/tools/cabana/ui/chart/chart.cc b/openpilot/tools/cabana/ui/chart/chart.cc index 399304186e..169a588eaa 100644 --- a/openpilot/tools/cabana/ui/chart/chart.cc +++ b/openpilot/tools/cabana/ui/chart/chart.cc @@ -139,7 +139,7 @@ void ChartView::updateLayout() { const ImVec2 top_left = layout_.rect.Min + ImVec2(LAYOUT_MARGINS.x, LAYOUT_MARGINS.y); layout_.move_icon_rect = ImRect(top_left, top_left + grip); const ImVec2 btn_size(iconButtonWidth(), iconButtonWidth()); - const ImVec2 close_min(layout_.rect.Max.x - LAYOUT_MARGINS.z - btn_size.x, top_left.y); + const ImVec2 close_min(layout_.rect.Max.x - std::max(LAYOUT_MARGINS.z, CONTROL_OUTLINE_PADDING) - btn_size.x, top_left.y); layout_.close_btn_rect = ImRect(close_min, close_min + btn_size); const ImVec2 manage_min(close_min.x - btn_size.x - ImGui::GetStyle().ItemInnerSpacing.x, top_left.y); layout_.manage_btn_rect = ImRect(manage_min, manage_min + btn_size); diff --git a/openpilot/tools/cabana/ui/util.cc b/openpilot/tools/cabana/ui/util.cc index f646f33da4..6269bd11c3 100644 --- a/openpilot/tools/cabana/ui/util.cc +++ b/openpilot/tools/cabana/ui/util.cc @@ -62,9 +62,21 @@ bool inputTextMultiline(const char *label, std::string *s, const ImVec2 &size, I inputCallback, &ctx); } +bool beginControlChild(const char *id, const ImVec2 &size, ImGuiWindowFlags flags) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(CONTROL_OUTLINE_PADDING, CONTROL_OUTLINE_PADDING)); + const bool visible = ImGui::BeginChild(id, size, ImGuiChildFlags_AlwaysUseWindowPadding, flags); + ImGui::PopStyleVar(); + return visible; +} + bool clearableInput(const char *label, std::string *s, const char *hint, ImGuiInputTextCallback validator) { + const float width = ImGui::CalcItemWidth(); + const float clear_width = iconButtonWidth() + ImGui::GetStyle().ItemInnerSpacing.x; + const bool show_clear = !s->empty() && width >= clear_width + ImGui::GetFrameHeight(); + ImGui::SetNextItemWidth(show_clear ? width - clear_width : width); + ImGui::BeginGroup(); bool changed = validatedInput(label, s, validator, hint); - if (!s->empty()) { + if (show_clear) { ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); ImGui::PushID(label); if (iconButton("clear", icon::X_LG)) { @@ -73,6 +85,7 @@ bool clearableInput(const char *label, std::string *s, const char *hint, ImGuiIn } ImGui::PopID(); } + ImGui::EndGroup(); return changed; } diff --git a/openpilot/tools/cabana/ui/util.h b/openpilot/tools/cabana/ui/util.h index 87afb28f74..98c814b6c4 100644 --- a/openpilot/tools/cabana/ui/util.h +++ b/openpilot/tools/cabana/ui/util.h @@ -31,7 +31,11 @@ inline bool inputText(const char *label, std::string *s, const char *hint = "", bool inputTextMultiline(const char *label, std::string *s, const ImVec2 &size, ImGuiInputTextFlags flags = 0); -// an input with a trailing clear button once it holds text; true when the text changed +constexpr float CONTROL_OUTLINE_PADDING = 1.0f; +// Always pair with ImGui::EndChild(), even when false is returned. +bool beginControlChild(const char *id, const ImVec2 &size, ImGuiWindowFlags flags = 0); + +// SetNextItemWidth includes the field and clear button. Returns true when text changes. bool clearableInput(const char *label, std::string *s, const char *hint = "", ImGuiInputTextCallback validator = nullptr); bool comboBox(const char *label, int *index, const std::vector &items); diff --git a/openpilot/tools/cabana/ui/widgets/historylog.cc b/openpilot/tools/cabana/ui/widgets/historylog.cc index ba51c4d6ee..19ce2c9f65 100644 --- a/openpilot/tools/cabana/ui/widgets/historylog.cc +++ b/openpilot/tools/cabana/ui/widgets/historylog.cc @@ -132,11 +132,13 @@ void LogsWidget::exportToCSV() { void LogsWidget::draw() { const ImGuiStyle &style = ImGui::GetStyle(); + beginControlChild("toolbar", ImVec2(0, ImGui::GetFrameHeight() + CONTROL_OUTLINE_PADDING * 2), + ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + // toolbar: the export button is right aligned and never clipped, the value input shrinks first const float export_w = iconButtonWidth(); if (!sigs_.empty()) { - const float clear_w = value_edit_.empty() ? 0.0f : iconButtonWidth(); - const float fixed = DISPLAY_TYPE_WIDTH + SIGNALS_WIDTH + COMPARE_WIDTH + clear_w + style.ItemSpacing.x * 4 + export_w; + const float fixed = DISPLAY_TYPE_WIDTH + SIGNALS_WIDTH + COMPARE_WIDTH + style.ItemSpacing.x * 4 + export_w; const float value_w = std::clamp(ImGui::GetContentRegionAvail().x - fixed, 30.0f, 120.0f); ImGui::SetNextItemWidth(DISPLAY_TYPE_WIDTH); @@ -169,6 +171,7 @@ void LogsWidget::draw() { if (iconButton("export_csv", icon::FILETYPE_CSV)) exportToCSV(); ImGui::EndDisabled(); disabledItemTooltip("Export to CSV file..."); + ImGui::EndChild(); ImGui::Separator(); drawTable(); diff --git a/openpilot/tools/cabana/ui/widgets/messageswidget.cc b/openpilot/tools/cabana/ui/widgets/messageswidget.cc index 6de1735d79..08148107a0 100644 --- a/openpilot/tools/cabana/ui/widgets/messageswidget.cc +++ b/openpilot/tools/cabana/ui/widgets/messageswidget.cc @@ -434,12 +434,11 @@ void MessagesWidget::drawHeader() { } // the filter editors under the header - const float clear_width = iconButtonWidth(); ImGui::TableNextRow(); for (int i = 0; i < MessageList::COLUMN_COUNT; i++) { if (!ImGui::TableSetColumnIndex(i)) continue; ImGui::PushID(i); - ImGui::SetNextItemWidth(filters_[i].empty() ? -FLT_MIN : std::max(1.0f, ImGui::GetContentRegionAvail().x - clear_width)); + ImGui::SetNextItemWidth(-FLT_MIN); const std::string placeholder = std::string("Filter ") + COLUMN_TITLES[i]; if (clearableInput("##filter", &filters_[i], placeholder.c_str())) { std::map filters; diff --git a/openpilot/tools/cabana/ui/widgets/signalview.cc b/openpilot/tools/cabana/ui/widgets/signalview.cc index c0ed091568..2413912ea2 100644 --- a/openpilot/tools/cabana/ui/widgets/signalview.cc +++ b/openpilot/tools/cabana/ui/widgets/signalview.cc @@ -737,10 +737,8 @@ void SignalView::collapseAll() { void SignalView::drawTree() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); const float min_height = std::max(ImGui::GetContentRegionAvail().y, 300.0f); - const bool visible = ImGui::BeginChild("tree", ImVec2(0, min_height), ImGuiChildFlags_None); - ImGui::PopStyleVar(); + const bool visible = beginControlChild("tree", ImVec2(0, min_height)); if (visible) { DrawContext ctx{ImGui::GetWindowDrawList(), ImGui::GetCursorScreenPos().x, ImGui::GetContentRegionAvail().x, rowHeight()}; // the press that closes an open editor is consumed by the focus change, the index widgets never see it