From a989bc0b50db714c9fa4f4bdbc789662c40291b6 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:13:15 -0700 Subject: [PATCH] cabana: misc UI tweaks (#38782) --- openpilot/tools/cabana/tests/test_cabana.cc | 6 ++-- openpilot/tools/cabana/ui/chart/chart.cc | 2 +- openpilot/tools/cabana/ui/icons.h | 32 +++++++++---------- openpilot/tools/cabana/ui/mainwin.cc | 3 +- openpilot/tools/cabana/ui/style.cc | 12 +++---- .../tools/cabana/ui/widgets/historylog.cc | 4 +-- .../tools/cabana/ui/widgets/videowidget.cc | 13 ++++---- .../tools/cabana/ui/widgets/videowidget.h | 2 +- openpilot/tools/cabana/utils/strings.cc | 6 ++-- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/openpilot/tools/cabana/tests/test_cabana.cc b/openpilot/tools/cabana/tests/test_cabana.cc index 09741cd578..3523a47e08 100644 --- a/openpilot/tools/cabana/tests/test_cabana.cc +++ b/openpilot/tools/cabana/tests/test_cabana.cc @@ -195,10 +195,10 @@ void test_format_seconds() { REQUIRE(utils::formatSeconds(0) == "00:00"); REQUIRE(utils::formatSeconds(59.4) == "00:59"); REQUIRE(utils::formatSeconds(-1) == "00:00"); - REQUIRE(utils::formatSeconds(61.234, true) == "01:01.234"); + REQUIRE(utils::formatSeconds(61.234, true) == "01:01.23"); REQUIRE(utils::formatSeconds(3599.9) == "59:59"); REQUIRE(utils::formatSeconds(3601) == "01:00:01"); - REQUIRE(utils::formatSeconds(3601.5, true) == "01:00:01.500"); + REQUIRE(utils::formatSeconds(3601.5, true) == "01:00:01.50"); const char *tz = getenv("TZ"); const bool had_tz = tz != nullptr; @@ -206,7 +206,7 @@ void test_format_seconds() { setenv("TZ", "UTC", 1); tzset(); REQUIRE(utils::formatSeconds(0, false, true) == "1970-01-01 00:00:00"); - REQUIRE(utils::formatSeconds(1700000000.123, true, true) == "2023-11-14 22:13:20.123"); + REQUIRE(utils::formatSeconds(1700000000.123, true, true) == "2023-11-14 22:13:20.12"); if (had_tz) { setenv("TZ", saved_tz.c_str(), 1); } else { diff --git a/openpilot/tools/cabana/ui/chart/chart.cc b/openpilot/tools/cabana/ui/chart/chart.cc index 4dedc9074c..5d1389822e 100644 --- a/openpilot/tools/cabana/ui/chart/chart.cc +++ b/openpilot/tools/cabana/ui/chart/chart.cc @@ -488,7 +488,7 @@ void ChartView::showTip(double sec) { x = tooltip_x_; } ImVec2 pt(x, layout_.plot_area.Min.y); - text_list.insert(text_list.begin(), TipLine{.name = formatNumber(secondsAtPoint({x, 0}), 3)}); + text_list.insert(text_list.begin(), TipLine{.name = formatNumber(secondsAtPoint({x, 0}), 2)}); tip_label_.showText(pt, text_list, visible_rect); } diff --git a/openpilot/tools/cabana/ui/icons.h b/openpilot/tools/cabana/ui/icons.h index 3a736fb3c9..5c97666da4 100644 --- a/openpilot/tools/cabana/ui/icons.h +++ b/openpilot/tools/cabana/ui/icons.h @@ -4,37 +4,37 @@ namespace icon { constexpr const char ARROW_CLOCKWISE[] = "\xef\x84\x96"; constexpr const char ARROW_COUNTERCLOCKWISE[] = "\xef\x84\x97"; -constexpr const char ARROW_DOWN_LEFT_SQUARE[] = "\xef\x84\x9d"; -constexpr const char ARROW_UP_RIGHT_SQUARE[] = "\xef\x85\x83"; +constexpr const char ARROW_DOWN_LEFT_SQUARE[] = "\xef\x84\x9c"; +constexpr const char ARROW_UP_RIGHT_SQUARE[] = "\xef\x85\x82"; constexpr const char CHEVRON_LEFT[] = "\xef\x8a\x84"; constexpr const char CHEVRON_RIGHT[] = "\xef\x8a\x85"; constexpr const char DASH[] = "\xef\x8b\xaa"; -constexpr const char DASH_SQUARE[] = "\xef\x8b\xa9"; -constexpr const char EXCLAMATION_TRIANGLE[] = "\xef\x8c\xbb"; -constexpr const char FAST_FORWARD[] = "\xef\x9f\xb4"; +constexpr const char DASH_SQUARE[] = "\xef\x8b\xa8"; +constexpr const char EXCLAMATION_TRIANGLE[] = "\xef\x8c\xba"; +constexpr const char FAST_FORWARD[] = "\xef\x9f\xb3"; constexpr const char FILETYPE_CSV[] = "\xef\x9d\x83"; -constexpr const char FOLDER[] = "\xef\x8f\x99"; -constexpr const char FILE_EARMARK[] = "\xef\x8e\x92"; -constexpr const char FILE_EARMARK_RULED[] = "\xef\x8e\x85"; -constexpr const char PLUS_SQUARE[] = "\xef\x93\xbd"; +constexpr const char FOLDER[] = "\xef\x8f\x91"; +constexpr const char FILE_EARMARK[] = "\xef\x8d\xa9"; +constexpr const char FILE_EARMARK_RULED[] = "\xef\x8e\x84"; +constexpr const char PLUS_SQUARE[] = "\xef\x93\xbc"; constexpr const char GRAPH_UP[] = "\xef\x8f\xb2"; constexpr const char GRIP_HORIZONTAL[] = "\xef\x8f\xbd"; -constexpr const char INFO_CIRCLE[] = "\xef\x90\xb1"; +constexpr const char INFO_CIRCLE[] = "\xef\x90\xb0"; constexpr const char LIST[] = "\xef\x91\xb9"; -constexpr const char PAUSE[] = "\xef\x93\x84"; +constexpr const char PAUSE[] = "\xef\x93\x83"; constexpr const char PENCIL[] = "\xef\x93\x8b"; -constexpr const char PLAY[] = "\xef\x93\xb5"; +constexpr const char PLAY[] = "\xef\x93\xb4"; constexpr const char PLUS[] = "\xef\x93\xbe"; constexpr const char RAQUO[] = "\xc2\xbb"; // U+00BB, not a bootstrap icon: the toolbar extension button constexpr const char REPEAT[] = "\xef\xa0\x93"; constexpr const char REPEAT_1[] = "\xef\xa0\x92"; -constexpr const char REWIND[] = "\xef\xa0\x99"; -constexpr const char SKIP_END[] = "\xef\x95\x98"; -constexpr const char STOPWATCH[] = "\xef\x96\x97"; +constexpr const char REWIND[] = "\xef\xa0\x98"; +constexpr const char SKIP_END[] = "\xef\x95\x97"; +constexpr const char STOPWATCH[] = "\xef\x96\x96"; constexpr const char THREE_DOTS[] = "\xef\x97\x94"; constexpr const char WINDOW_STACK[] = "\xef\x9b\x92"; constexpr const char X[] = "\xef\x98\xaa"; constexpr const char X_LG[] = "\xef\x99\x99"; -constexpr const char X_SQUARE[] = "\xef\x98\xa9"; +constexpr const char X_SQUARE[] = "\xef\x98\xa8"; constexpr const char ZOOM_OUT[] = "\xef\x98\xad"; } // namespace icon diff --git a/openpilot/tools/cabana/ui/mainwin.cc b/openpilot/tools/cabana/ui/mainwin.cc index 4a0aef7c79..80a6157d54 100644 --- a/openpilot/tools/cabana/ui/mainwin.cc +++ b/openpilot/tools/cabana/ui/mainwin.cc @@ -835,7 +835,8 @@ void MainWindow::drawVideoPanel() { const bool live = can->liveStreaming(); // the bordered child pads its content, so the heights the widget asks for grow by the padding const float video_padding = ImGui::GetStyle().WindowPadding.y * 2.0f; - const float default_h = video_widget_->defaultHeight(avail.x) + video_padding; + // the camera is as wide as the child's content region, not the panel + const float default_h = video_widget_->defaultHeight(avail.x - ImGui::GetStyle().WindowPadding.x * 2.0f) + video_padding; const float video_hint = video_splitter_ratio_ >= 0.0f ? avail.y * video_splitter_ratio_ : default_h; float video_h = charts_floating_ ? avail.y : std::clamp(video_hint, 0.0f, avail.y - 1.0f); if (live) video_h = default_h; // display video at minimum size. diff --git a/openpilot/tools/cabana/ui/style.cc b/openpilot/tools/cabana/ui/style.cc index 8cbedad837..a006dbc66d 100644 --- a/openpilot/tools/cabana/ui/style.cc +++ b/openpilot/tools/cabana/ui/style.cc @@ -100,9 +100,9 @@ void applyTheme(int theme) { colors[ImGuiCol_FrameBg] = c(DarkTheme::base); colors[ImGuiCol_FrameBgHovered] = colorRgb(0x1f, 0x1f, 0x1f); colors[ImGuiCol_FrameBgActive] = colorRgb(0x24, 0x24, 0x24); - colors[ImGuiCol_Button] = colorRgb(0x3a, 0x3a, 0x3a); - colors[ImGuiCol_ButtonHovered] = colorRgb(0x42, 0x42, 0x42); - colors[ImGuiCol_ButtonActive] = colorRgb(0x30, 0x30, 0x30); + colors[ImGuiCol_Button] = colorRgb(0x5e, 0x5e, 0x5e); + colors[ImGuiCol_ButtonHovered] = colorRgb(0x6a, 0x6a, 0x6a); + colors[ImGuiCol_ButtonActive] = colorRgb(0x52, 0x52, 0x52); colors[ImGuiCol_Header] = highlight; colors[ImGuiCol_HeaderHovered] = c(DarkTheme::highlight, 0.8f); colors[ImGuiCol_HeaderActive] = highlight; @@ -157,9 +157,9 @@ void applyTheme(int theme) { colors[ImGuiCol_FrameBg] = base; colors[ImGuiCol_FrameBgHovered] = colorRgb(0xf7, 0xf7, 0xf7); colors[ImGuiCol_FrameBgActive] = colorRgb(0xef, 0xef, 0xef); - colors[ImGuiCol_Button] = colorRgb(0xf5, 0xf5, 0xf5); - colors[ImGuiCol_ButtonHovered] = colorRgb(0xfa, 0xfa, 0xfa); - colors[ImGuiCol_ButtonActive] = colorRgb(0xd9, 0xd9, 0xd9); + colors[ImGuiCol_Button] = colorRgb(0xe4, 0xe4, 0xe4); + colors[ImGuiCol_ButtonHovered] = colorRgb(0xec, 0xec, 0xec); + colors[ImGuiCol_ButtonActive] = colorRgb(0xd0, 0xd0, 0xd0); colors[ImGuiCol_Header] = highlight; colors[ImGuiCol_HeaderHovered] = colorRgb(0x30, 0x8c, 0xc6, 0.8f); colors[ImGuiCol_HeaderActive] = highlight; diff --git a/openpilot/tools/cabana/ui/widgets/historylog.cc b/openpilot/tools/cabana/ui/widgets/historylog.cc index 0a3de51d70..ec2ef844ff 100644 --- a/openpilot/tools/cabana/ui/widgets/historylog.cc +++ b/openpilot/tools/cabana/ui/widgets/historylog.cc @@ -22,7 +22,7 @@ constexpr float COMPARE_WIDTH = 50.0f; std::string formatTime(uint64_t mono_time) { char buf[32] = {}; - snprintf(buf, sizeof(buf), "%.3f", can->toSeconds(mono_time)); + snprintf(buf, sizeof(buf), "%.2f", can->toSeconds(mono_time)); return buf; } @@ -184,7 +184,7 @@ std::string LogsWidget::headerText(int column) const { } ImVec2 LogsWidget::headerSize(int column, float viewport_width) const { - const ImVec2 time_text_size = ImGui::CalcTextSize("000000.000"); + const ImVec2 time_text_size = ImGui::CalcTextSize("000000.00"); const ImVec2 time_col_size(time_text_size.x + 10, time_text_size.y + 6); if (column == 0) return time_col_size; const int default_size = std::max(100, (int)((viewport_width - time_col_size.x) / (columnCount() - 1))); diff --git a/openpilot/tools/cabana/ui/widgets/videowidget.cc b/openpilot/tools/cabana/ui/widgets/videowidget.cc index fa6b9c7e6c..ed5683f74b 100644 --- a/openpilot/tools/cabana/ui/widgets/videowidget.cc +++ b/openpilot/tools/cabana/ui/widgets/videowidget.cc @@ -534,20 +534,19 @@ void StreamCameraView::draw(const ImVec2 &size, double thumbnail_time) { } } -const RgbImage *StreamCameraView::thumbnailAt(double sec, uint64_t *mono_time) { +const RgbImage *StreamCameraView::thumbnailAt(double sec) { auto it = big_thumbnails_.lower_bound(can->toMonoTime(sec)); if (it == big_thumbnails_.end()) return nullptr; if (big_thumbnail_texture_.id == 0 || big_thumbnail_texture_.key != it->first) { big_thumbnail_texture_.upload(it->second); big_thumbnail_texture_.key = it->first; } - if (mono_time) *mono_time = it->first; return &it->second; } void StreamCameraView::drawScrubThumbnail(ImDrawList *p, double sec) { p->AddRectFilled(rect().Min, rect().Max, IM_COL32(0, 0, 0, 255)); - if (const RgbImage *image = thumbnailAt(sec, nullptr)) { + if (const RgbImage *image = thumbnailAt(sec)) { // scale to the widget size, keeping the aspect ratio const float scale = std::min(width() / image->width, height() / image->height); const ImVec2 scaled_size(std::floor(image->width * scale), std::floor(image->height * scale)); @@ -560,8 +559,7 @@ void StreamCameraView::drawScrubThumbnail(ImDrawList *p, double sec) { } void StreamCameraView::drawThumbnail(ImDrawList *p, double sec) { - uint64_t mono_time = 0; - if (const RgbImage *image = thumbnailAt(sec, &mono_time)) { + if (const RgbImage *image = thumbnailAt(sec)) { // AddImage scales the stored image to the thumbnail height, keeping the aspect ratio const int h = MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2; const int w = std::max(1, (int)std::lround((double)image->width * h / image->height)); @@ -574,7 +572,8 @@ void StreamCameraView::drawThumbnail(ImDrawList *p, double sec) { ImRect thumb_rect(ImVec2(rect().Min.x + x, rect().Min.y + y), ImVec2(rect().Min.x + x + w, rect().Min.y + y + h)); p->AddImage(big_thumbnail_texture_.ref(), thumb_rect.Min, thumb_rect.Max); p->AddRect(thumb_rect.Min, thumb_rect.Max, paletteBrightText(), 0.0f, 0, 2.0f); - if (auto alert = getReplay()->findAlertAtTime(can->toSeconds(mono_time))) { + // look up the alert at the hovered time, the thumbnail frame itself can be seconds away + if (auto alert = getReplay()->findAlertAtTime(sec)) { drawAlert(p, thumb_rect, *alert, POINT_10_FONT_SIZE); } drawTime(p, thumb_rect, sec); @@ -583,7 +582,7 @@ void StreamCameraView::drawThumbnail(ImDrawList *p, double sec) { void StreamCameraView::drawTime(ImDrawList *p, const ImRect &rect, double seconds) { char text[32]; - snprintf(text, sizeof(text), "%.3f", seconds); + snprintf(text, sizeof(text), "%.2f", seconds); ImFont *font = ImGui::GetFont(); const ImVec2 text_size = font->CalcTextSizeA(POINT_10_FONT_SIZE, FLT_MAX, 0.0f, text); // centered horizontally, above the bottom margin diff --git a/openpilot/tools/cabana/ui/widgets/videowidget.h b/openpilot/tools/cabana/ui/widgets/videowidget.h index 44c3cd5312..d873c3ed10 100644 --- a/openpilot/tools/cabana/ui/widgets/videowidget.h +++ b/openpilot/tools/cabana/ui/widgets/videowidget.h @@ -69,7 +69,7 @@ private: }; void collectThumbnails(); // moves the decoded thumbnails in once a parseQLog task is done // the first thumbnail at or after sec, uploaded to big_thumbnail_texture_; nullptr when there is none - const RgbImage *thumbnailAt(double sec, uint64_t *mono_time); + const RgbImage *thumbnailAt(double sec); void drawAlert(ImDrawList *p, const ImRect &rect, const Timeline::Entry &alert, float font_size); void drawThumbnail(ImDrawList *p, double sec); void drawScrubThumbnail(ImDrawList *p, double sec); diff --git a/openpilot/tools/cabana/utils/strings.cc b/openpilot/tools/cabana/utils/strings.cc index 5590b6078e..3a1191609e 100644 --- a/openpilot/tools/cabana/utils/strings.cc +++ b/openpilot/tools/cabana/utils/strings.cc @@ -21,7 +21,7 @@ std::string formatSeconds(double sec, bool include_milliseconds, bool absolute_t char buf[64] = {}; std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm); if (!include_milliseconds) return buf; - snprintf(out, sizeof(out), "%s.%03d", buf, millis); + snprintf(out, sizeof(out), "%s.%02d", buf, millis / 10); return out; } @@ -33,11 +33,11 @@ std::string formatSeconds(double sec, bool include_milliseconds, bool absolute_t const int seconds = (total_ms / 1000) % 60; const int millis = total_ms % 1000; if (show_hours && include_milliseconds) { - snprintf(out, sizeof(out), "%02d:%02d:%02d.%03d", hours, minutes, seconds, millis); + snprintf(out, sizeof(out), "%02d:%02d:%02d.%02d", hours, minutes, seconds, millis / 10); } else if (show_hours) { snprintf(out, sizeof(out), "%02d:%02d:%02d", hours, minutes, seconds); } else if (include_milliseconds) { - snprintf(out, sizeof(out), "%02d:%02d.%03d", minutes, seconds, millis); + snprintf(out, sizeof(out), "%02d:%02d.%02d", minutes, seconds, millis / 10); } else { snprintf(out, sizeof(out), "%02d:%02d", minutes, seconds); }