diff --git a/openpilot/tools/cabana/ui/chart/chart.cc b/openpilot/tools/cabana/ui/chart/chart.cc index 1c319b4faf..57d00a2ee2 100644 --- a/openpilot/tools/cabana/ui/chart/chart.cc +++ b/openpilot/tools/cabana/ui/chart/chart.cc @@ -475,7 +475,7 @@ void ChartView::showTip(double sec) { std::string name = s.sig->name; std::string min = s.min == std::numeric_limits::max() ? "--" : utils::toString(s.min); std::string max = s.max == std::numeric_limits::lowest() ? "--" : utils::toString(s.max); - text_list.push_back({.has_marker = true, .marker = toImU32(s.color), .name = name, .value = value, .min = min, .max = max}); + text_list.push_back({.has_marker = true, .marker = toImU32(graphicColor(s.color)), .name = name, .value = value, .min = min, .max = max}); } } if (x < 0) { @@ -620,9 +620,9 @@ void ChartView::drawLegend() { ImGui::PopID(); if (series_type_ == SeriesType::Scatter) { - painter->AddCircleFilled(r.Min + ImVec2(marker_size / 2.0f, 2.0f + marker_size / 2.0f), marker_size / 2.0f, toImU32(s.color)); + painter->AddCircleFilled(r.Min + ImVec2(marker_size / 2.0f, 2.0f + marker_size / 2.0f), marker_size / 2.0f, toImU32(graphicColor(s.color))); } else { - drawColorMarker(painter, r.Min, toImU32(s.color)); + drawColorMarker(painter, r.Min, toImU32(graphicColor(s.color))); } float x = r.Min.x + marker_size + ImGui::GetStyle().ItemInnerSpacing.x; @@ -655,7 +655,7 @@ void ChartView::drawSeries() { const std::string label = "##sig" + std::to_string(i); ImPlotSpec spec; - spec.LineColor = toImVec4(s.color); + spec.LineColor = toImVec4(graphicColor(s.color)); spec.Stride = sizeof(ImPlotPoint); if (series_type_ == SeriesType::Scatter) { float radius = std::clamp(pixels_per_point / 2.0, 2.0, 8.0) / 2.0; @@ -682,7 +682,7 @@ void ChartView::drawSeries() { // show points when zoomed in enough if ((num_points == 1 || pixels_per_point > 20) && first != last) { ImPlotSpec dots; - dots.LineColor = toImVec4(s.color); + dots.LineColor = toImVec4(graphicColor(s.color)); dots.Stride = sizeof(ImPlotPoint); dots.Marker = ImPlotMarker_Circle; dots.MarkerSize = 4; @@ -700,12 +700,12 @@ void ChartView::drawForeground() { for (auto &s : sigs_) { if (!isNull(s.track_pt) && s.visible) { ImVec2 pos(xPos(s.track_pt.x), yPos(s.track_pt.y)); - painter->AddCircleFilled(pos, 5.5f, toImU32(s.color.darker(125))); + painter->AddCircleFilled(pos, 5.5f, toImU32(graphicColor(s.color))); track_line_x = std::max(track_line_x, pos.x); } } if (track_line_x > 0) { - const ImU32 dark_gray = IM_COL32(0x80, 0x80, 0x80, 0xff); + const ImU32 dark_gray = ImGui::GetColorU32(palette().text_disabled); for (float y = layout_.plot_area.Min.y; y < layout_.plot_area.Max.y; y += 8) { painter->AddLine(ImVec2(track_line_x, y), ImVec2(track_line_x, std::min(y + 4, layout_.plot_area.Max.y)), dark_gray, 1.0f); } diff --git a/openpilot/tools/cabana/ui/chart/sparkline.cc b/openpilot/tools/cabana/ui/chart/sparkline.cc index 1e804f2765..d9e5702fb5 100644 --- a/openpilot/tools/cabana/ui/chart/sparkline.cc +++ b/openpilot/tools/cabana/ui/chart/sparkline.cc @@ -100,7 +100,7 @@ void Sparkline::render(const CabanaColor &color, int range, ImVec2 sz, double wi } size = sz; - color_ = toImU32(sparklineColor(color)); + color_ = color; draw_individual_points_ = draw_individual_points; window_end_ = window_end; xscale_ = xscale; @@ -108,7 +108,8 @@ void Sparkline::render(const CabanaColor &color, int range, ImVec2 sz, double wi void Sparkline::draw(ImDrawList *draw_list, ImVec2 pos, ImU32 color) const { if (render_points_.empty()) return; - if (color == 0) color = color_; + // Pick up theme changes while paused. + if (color == 0) color = toImU32(sparklineColor(color_)); // update() only runs when a message of this id arrives, so a slow message would hold the sparkline // still for many frames and then move it in one step. scroll the rendered polyline by the time that diff --git a/openpilot/tools/cabana/ui/chart/sparkline.h b/openpilot/tools/cabana/ui/chart/sparkline.h index 991ecb8d4c..9d5420f4b6 100644 --- a/openpilot/tools/cabana/ui/chart/sparkline.h +++ b/openpilot/tools/cabana/ui/chart/sparkline.h @@ -26,7 +26,7 @@ private: std::vector points_; std::vector render_points_; - ImU32 color_ = 0; + CabanaColor color_; double window_end_ = 0; // the time the polyline was built for, so draw() can scroll it on from there double xscale_ = 0; bool draw_individual_points_ = false; diff --git a/openpilot/tools/cabana/ui/helpoverlay.cc b/openpilot/tools/cabana/ui/helpoverlay.cc index bc64f99196..f89e8b0b7e 100644 --- a/openpilot/tools/cabana/ui/helpoverlay.cc +++ b/openpilot/tools/cabana/ui/helpoverlay.cc @@ -181,7 +181,9 @@ void HelpOverlay::draw() { float x = min.x + 8.0f; for (const auto &r : line) { const float w = run_width(r); - const ImU32 color = r.color ? r.color : ImGui::GetColorU32(ImGuiCol_Text); + const auto background = fromImVec4(r.chip ? palette().button : palette().surface); + const auto source = r.color ? fromImVec4(ImGui::ColorConvertU32ToFloat4(r.color)) : fromImVec4(palette().text); + const ImU32 color = toImU32(contrastColor(source, background, r.swatch ? 3.0 : 4.5)); if (r.swatch) { dl->AddRectFilled(ImVec2(x + 2, y + 3), ImVec2(x + font_size - 2, y + font_size - 1), color); } else { diff --git a/openpilot/tools/cabana/ui/theme.cc b/openpilot/tools/cabana/ui/theme.cc index 45212cb6d7..20cf2919ff 100644 --- a/openpilot/tools/cabana/ui/theme.cc +++ b/openpilot/tools/cabana/ui/theme.cc @@ -12,30 +12,30 @@ namespace fs = std::filesystem; namespace { constexpr Palette DARK_PALETTE = { - .text = rgb(0xffffff), .text_disabled = rgb(0x7b7b7b), .text_selected = rgb(0xffffff), + .text = rgb(0xffffff), .text_disabled = rgb(0xcccccc), .text_inactive = rgb(0xa4a4a4), .text_selected = rgb(0xffffff), .window = rgb(0x353535), .surface = rgb(0x2b2c2d), .frame = rgb(0x2b2c2d), .frame_hovered = rgb(0x363738), .frame_active = rgb(0x434445), .button = rgb(0x3f4041), .button_hovered = rgb(0x525354), .button_active = rgb(0x303132), - .header = rgb(0x2d74d2), .header_hovered = rgb(0x323944), .header_active = rgb(0x2d74d2), - .accent = rgb(0x2d74d2), - .border = rgb(0x606060), .separator = rgb(0x353535), .scrollbar_grab = rgb(0x4c4e4f), - .slider_track = rgb(0x7b7b7b), + .header = rgb(0x2d74d2), .header_hovered = rgb(0x323944), .header_active = rgb(0x434445), + .accent = rgb(0x90bfff), + .border = rgb(0xaaaaaa), .separator = rgb(0x353535), .scrollbar_grab = rgb(0xaaaaaa), + .slider_track = rgb(0xaaaaaa), .tab = rgb(0x2c2c2c), .tab_hovered = rgb(0x444647), .tab_selected = rgb(0x393a3b), .table_header = rgb(0x3f4041), - .grid = rgb(0xdddddd, 50.0f / 255.0f), .badge = rgb(0x808080), + .grid = rgb(0xdddddd, 50.0f / 255.0f), .badge = rgb(0x626262), .sparkline_saturation = 1.0f, .sparkline_value = 1.0f, }; constexpr Palette LIGHT_PALETTE = { - .text = rgb(0x000000), .text_disabled = rgb(0xbebebe), .text_selected = rgb(0xffffff), + .text = rgb(0x000000), .text_disabled = rgb(0x595959), .text_inactive = rgb(0x595959), .text_selected = rgb(0xffffff), .window = rgb(0xefefef), .surface = rgb(0xffffff), .frame = rgb(0xffffff), .frame_hovered = rgb(0xf5f9fc), .frame_active = rgb(0xe7f3fb), .button = rgb(0xefefef), .button_hovered = rgb(0xe7f3fb), .button_active = rgb(0xd4e7f4), - .header = rgb(0x308cc6), .header_hovered = rgb(0xe7f3fb), .header_active = rgb(0x308cc6), - .accent = rgb(0x308cc6), - .border = rgb(0xb6b6b6), .separator = rgb(0xd0d0d0), .scrollbar_grab = rgb(0xb6b6b6), - .slider_track = rgb(0xd0d0d0), + .header = rgb(0x226a99), .header_hovered = rgb(0xe7f3fb), .header_active = rgb(0xd4e7f4), + .accent = rgb(0x226a99), + .border = rgb(0x747474), .separator = rgb(0xd0d0d0), .scrollbar_grab = rgb(0x747474), + .slider_track = rgb(0x747474), .tab = rgb(0xe5e5e5), .tab_hovered = rgb(0xefefef), .tab_selected = rgb(0xffffff), .table_header = rgb(0xefefef), - .grid = rgb(0x000000, 50.0f / 255.0f), .badge = rgb(0xa0a0a4), + .grid = rgb(0x000000, 50.0f / 255.0f), .badge = rgb(0x626262), .sparkline_saturation = 2.0f, .sparkline_value = 0.7f, }; @@ -67,6 +67,36 @@ ImFont *addFont(const fs::path &path, float size) { return font; } +double linearChannel(double value) { + value /= 255.0; + return value <= 0.04045 ? value / 12.92 : std::pow((value + 0.055) / 1.055, 2.4); +} + +double luminance(const CabanaColor &color) { + return 0.2126 * linearChannel(color.r) + 0.7152 * linearChannel(color.g) + 0.0722 * linearChannel(color.b); +} + +// Directed rounding preserves the luminance bound in 8-bit sRGB. +CabanaColor boundLuminance(const CabanaColor &color, double bound, bool minimum) { + const double current = luminance(color); + if (minimum ? current >= bound : current <= bound) return color; + auto channel = [&](uint8_t value) { + double c = linearChannel(value); + c = minimum ? c + (1.0 - c) * (bound - current) / (1.0 - current) : c * bound / current; + const double srgb = c <= 0.0031308 ? 12.92 * c : 1.055 * std::pow(c, 1.0 / 2.4) - 0.055; + const double scaled = std::clamp(srgb * 255.0, 0.0, 255.0); + return static_cast(minimum ? std::ceil(scaled) : std::floor(scaled)); + }; + return {channel(color.r), channel(color.g), channel(color.b), color.a}; +} + +CabanaColor composite(const CabanaColor &color, const CabanaColor &base) { + auto channel = [&](int foreground, int background) { + return static_cast(std::lround((foreground * color.a + background * (255 - color.a)) / 255.0)); + }; + return {channel(color.r, base.r), channel(color.g, base.g), channel(color.b, base.b)}; +} + ImVec4 alpha(ImVec4 c, float a) { return ImVec4(c.x, c.y, c.z, a); } } // namespace @@ -123,7 +153,8 @@ void applyTheme(int theme) { c[ImGuiCol_FrameBgHovered] = p.frame_hovered; c[ImGuiCol_FrameBgActive] = p.frame_active; c[ImGuiCol_Button] = p.button; - c[ImGuiCol_ButtonHovered] = c[ImGuiCol_SliderGrab] = p.button_hovered; + c[ImGuiCol_ButtonHovered] = p.button_hovered; + c[ImGuiCol_SliderGrab] = p.accent; c[ImGuiCol_ScrollbarGrab] = p.scrollbar_grab; c[ImGuiCol_ScrollbarGrabHovered] = p.text_disabled; c[ImGuiCol_ButtonActive] = p.button_active; @@ -157,10 +188,52 @@ void applyTheme(int theme) { const Palette &palette() { return *g_palette; } +CabanaColor contrastColor(CabanaColor color, const CabanaColor &background, double target) { + color.a = 255; + const double value = luminance(color), base = luminance(background); + if ((std::max(value, base) + 0.05) / (std::min(value, base) + 0.05) >= target) return color; + const bool lighter = base < 0.179; + const double bound = lighter ? target * (base + 0.05) - 0.05 : (base + 0.05) / target - 0.05; + return boundLuminance(color, std::clamp(bound, 0.0, 1.0), lighter); +} + +CabanaColor byteColor(const CabanaColor &color) { + const bool dark = palette().text.x > 0.5f; + const auto [h, s, v] = color.hsv(); + auto fill = CabanaColor::fromHsv(h, std::min(s, 0.30f), 0.86f, color.alphaF()); + if (dark) fill = boundLuminance(fill, 0.16, false); // 5:1 with white text + fill = composite(fill, fromImVec4(palette().surface)); + return dark ? fill : composite({255, 255, 255, 40}, fill); +} + +CabanaColor signalFill(const CabanaColor &color, bool defined) { + const bool dark = palette().text.x > 0.5f; + auto fill = composite(color, fromImVec4(palette().surface)); + fill = composite(dark ? CabanaColor(0, 0, 0, 115) : CabanaColor(255, 255, 255, defined ? 20 : 40), fill); + return boundLuminance(fill, 0.18, !dark); // 4.5:1 with the theme's text +} + +CabanaColor signalHighlight(CabanaColor color) { + color.a = 255; + return boundLuminance(boundLuminance(color, 0.175, true), 0.18, false); +} + +CabanaColor signalOutline(CabanaColor color, bool hovered) { + const bool dark = palette().text.x > 0.5f; + if (dark && hovered) color = color.lighter(150); + color.a = 255; + // 3:1 against normal and highlighted fills, including neighboring signals. + return dark ? boundLuminance(color, 0.67, true) : boundLuminance(color, 0.02, false); +} + +CabanaColor graphicColor(const CabanaColor &color, const ImVec4 &background) { + return contrastColor(color, fromImVec4(background), 3.0); +} + CabanaColor sparklineColor(const CabanaColor &color) { const Palette &p = palette(); auto [h, s, v] = color.hsv(); - return CabanaColor::fromHsv(h, std::min(1.0f, s * p.sparkline_saturation), v * p.sparkline_value, color.a / 255.0f); + return graphicColor(CabanaColor::fromHsv(h, std::min(1.0f, s * p.sparkline_saturation), v * p.sparkline_value)); } ImFont *boldFont() { return g_bold_font; } diff --git a/openpilot/tools/cabana/ui/theme.h b/openpilot/tools/cabana/ui/theme.h index 0c565b55c7..4655219505 100644 --- a/openpilot/tools/cabana/ui/theme.h +++ b/openpilot/tools/cabana/ui/theme.h @@ -6,12 +6,12 @@ #include "tools/cabana/core/color.h" struct Palette { - ImVec4 text, text_disabled, text_selected; + ImVec4 text, text_disabled, text_inactive, text_selected; ImVec4 window; // the background behind panels and docked windows ImVec4 surface; // panels, popups, table bodies: what content is drawn on ImVec4 frame, frame_hovered, frame_active; ImVec4 button, button_hovered, button_active; - ImVec4 header, header_hovered, header_active; // selections + ImVec4 header, header_hovered, header_active; // selected fill; unselected hover/press fills ImVec4 accent; ImVec4 border, separator, scrollbar_grab; ImVec4 slider_track; @@ -28,6 +28,10 @@ inline ImVec4 colorRgb(int r, int g, int b, float alpha = 1.0f) { return ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, alpha); } inline ImU32 toImU32(const CabanaColor &c) { return IM_COL32(c.r, c.g, c.b, c.a); } +inline CabanaColor fromImVec4(const ImVec4 &c) { + return {static_cast(std::lround(c.x * 255)), static_cast(std::lround(c.y * 255)), + static_cast(std::lround(c.z * 255)), static_cast(std::lround(c.w * 255))}; +} inline ImVec4 toImVec4(const CabanaColor &c) { return ImVec4(c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, c.a / 255.0f); } inline ImU32 withAlpha(ImU32 c, int alpha) { return (c & ~IM_COL32_A_MASK) | ((ImU32)alpha << IM_COL32_A_SHIFT); } @@ -44,6 +48,12 @@ constexpr float UI_FONT_SIZE = 16.0f; void loadFonts(); void applyTheme(int theme); // Safe to call at runtime. const Palette &palette(); +CabanaColor contrastColor(CabanaColor color, const CabanaColor &background, double target = 4.5); +CabanaColor byteColor(const CabanaColor &color); +CabanaColor signalFill(const CabanaColor &color, bool defined = true); +CabanaColor signalHighlight(CabanaColor color); +CabanaColor signalOutline(CabanaColor color, bool hovered = false); +CabanaColor graphicColor(const CabanaColor &color, const ImVec4 &background = palette().surface); CabanaColor sparklineColor(const CabanaColor &color); ImFont *boldFont(); diff --git a/openpilot/tools/cabana/ui/util.cc b/openpilot/tools/cabana/ui/util.cc index b11ecf28fa..2252355144 100644 --- a/openpilot/tools/cabana/ui/util.cc +++ b/openpilot/tools/cabana/ui/util.cc @@ -94,9 +94,10 @@ bool selectable(const char *label, bool selected, ImGuiSelectableFlags flags, co if (selected) { ImGui::PushStyleColor(ImGuiCol_Text, palette().text_selected); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, palette().header); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, palette().header); } const bool clicked = ImGui::Selectable(label, selected, flags, size); - if (selected) ImGui::PopStyleColor(2); + if (selected) ImGui::PopStyleColor(3); return clicked; } @@ -382,7 +383,7 @@ int tableHeadersRow() { bool viewSelectable(const char *label, bool selected, ImGuiSelectableFlags flags, const ImVec2 &size) { ImGui::PushStyleColor(ImGuiCol_HeaderHovered, selected ? ImGui::GetColorU32(ImGuiCol_Header) : IM_COL32(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetColorU32(ImGuiCol_Header)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, selected ? palette().header : palette().header_active); const bool clicked = selectable(label, selected, flags, size); ImGui::PopStyleColor(2); return clicked; diff --git a/openpilot/tools/cabana/ui/widgets/binaryview.cc b/openpilot/tools/cabana/ui/widgets/binaryview.cc index df17944447..9d983b918d 100644 --- a/openpilot/tools/cabana/ui/widgets/binaryview.cc +++ b/openpilot/tools/cabana/ui/widgets/binaryview.cc @@ -22,10 +22,8 @@ const float SMALL_FONT_SIZE = 10.0f; // Inter needs 10 px for a 7 px cap height const int VERTICAL_HEADER_WIDTH = 30; inline int get_bit_pos(const BinaryIndex &index) { return flipBitPos(index.row * 8 + index.column); } -inline ImU32 paletteHighlight() { return ImGui::GetColorU32(ImGuiCol_Header); } inline ImU32 paletteBase() { return ImGui::GetColorU32(ImGuiCol_ChildBg); } -inline ImU32 paletteText(bool active) { return ImGui::GetColorU32(active ? ImGuiCol_Text : ImGuiCol_TextDisabled); } -const ImU32 DARK_GRAY = IM_COL32(128, 128, 128, 255); +inline ImU32 paletteText() { return ImGui::GetColorU32(ImGuiCol_Text); } // JetBrains Mono ships no bold variant, so emulate one by drawing the glyphs again a fraction of a // pixel to the right. Keeps the monospace advance, unlike switching to the proportional bold face. @@ -332,7 +330,7 @@ void BinaryView::draw() { for (int row = 0; row < rows; ++row) { const ImRect r(grid_pos_.x, grid_pos_.y + row * CELL_HEIGHT, grid_pos_.x + VERTICAL_HEADER_WIDTH, grid_pos_.y + (row + 1) * CELL_HEIGHT); painter->AddRectFilled(r.Min, r.Max, ImGui::GetColorU32(ImGuiCol_WindowBg)); // plain header background - drawText(painter, r, std::to_string(row).c_str(), paletteText(true)); + drawText(painter, r, std::to_string(row).c_str(), paletteText()); } for (int row = 0; row < rows; ++row) { for (int column = 0; column < COLUMN_COUNT; ++column) { @@ -457,7 +455,7 @@ void BinaryView::paintCell(ImDrawList *painter, const ImRect &rect, const Binary auto item = &cellAt(index); ImFont *font = ImGui::GetFont(); float font_size = CELL_FONT_SIZE; - ImU32 pen = paletteText(true); + ImU32 pen = paletteText(); if (index.column == HEX_COLUMN) { if (item->valid) { @@ -465,31 +463,30 @@ void BinaryView::paintCell(ImDrawList *painter, const ImRect &rect, const Binary font = ImGui::GetFont(); font_size = ImGui::GetFontSize(); popMonoFont(); - painter->AddRectFilled(rect.Min, rect.Max, toImU32(item->bg_color)); + painter->AddRectFilled(rect.Min, rect.Max, toImU32(byteColor(item->bg_color))); } } else if (isSelected(index)) { - painter->AddRectFilled(rect.Min, rect.Max, resize_sig_ ? toImU32(resize_sig_->color) : paletteHighlight()); + painter->AddRectFilled(rect.Min, rect.Max, toImU32(signalHighlight(resize_sig_ ? resize_sig_->color : fromImVec4(palette().header)))); pen = IM_COL32_WHITE; } else if (!hasSelection() || std::find(item->sigs.begin(), item->sigs.end(), resize_sig_) == item->sigs.end()) { // not resizing if (item->sigs.size() > 0) { for (auto &s : item->sigs) { - if (s == hovered_sig_) { - painter->AddRectFilled(rect.Min, rect.Max, toImU32(s->color.darker(125))); // 4/5x brightness - } else { - drawSignalCell(painter, rect, index, s); - } + drawSignalCell(painter, rect, index, s); } } else if (item->valid) { - if (item->bg_color.alpha() > 0) painter->AddRectFilled(rect.Min, rect.Max, toImU32(item->bg_color)); + if (item->bg_color.alpha() > 0) painter->AddRectFilled(rect.Min, rect.Max, toImU32(signalFill(item->bg_color, false))); } bool bright = std::find(item->sigs.begin(), item->sigs.end(), hovered_sig_) != item->sigs.end(); - pen = bright ? IM_COL32_WHITE : paletteText(is_message_active_); + if (bright) painter->AddRectFilled(rect.Min, rect.Max, toImU32(signalHighlight(hovered_sig_->color))); + pen = bright ? IM_COL32_WHITE : paletteText(); } + const auto text = fromImVec4(ImGui::ColorConvertU32ToFloat4(pen)); + const ImU32 pattern = toImU32(contrastColor({128, 128, 128}, text)); if (item->sigs.size() > 1) { - fillDense7Pattern(painter, rect, DARK_GRAY); + fillDense7Pattern(painter, rect, pattern); } else if (!item->valid) { - fillBDiagPattern(painter, rect, DARK_GRAY); + fillBDiagPattern(painter, rect, pattern); } if (item->valid) { if (index.column == HEX_COLUMN) { @@ -545,13 +542,13 @@ void BinaryView::drawSignalCell(ImDrawList *painter, const ImRect &rect, const B auto item = &cellAt(index); CabanaColor color = sig->color; color.a = item->bg_color.alpha(); - const ImU32 edge = toImU32(sig->color.darker(125)); + const ImU32 edge = toImU32(signalOutline(sig->color)); for (const ImRect &clip : region) { painter->PushClipRect(clip.Min, clip.Max, true); // mix the signal color with the background to fade it painter->AddRectFilled(rc.Min, rc.Max, paletteBase()); - painter->AddRectFilled(rc.Min, rc.Max, toImU32(color)); + painter->AddRectFilled(rc.Min, rc.Max, toImU32(signalFill(color))); if (draw_left) painter->AddLine(ImVec2(rc.Min.x + 0.5f, rc.Min.y), ImVec2(rc.Min.x + 0.5f, rc.Max.y), edge, 1.0f); if (draw_right) painter->AddLine(ImVec2(rc.Max.x - 0.5f, rc.Min.y), ImVec2(rc.Max.x - 0.5f, rc.Max.y), edge, 1.0f); diff --git a/openpilot/tools/cabana/ui/widgets/detailwidget.cc b/openpilot/tools/cabana/ui/widgets/detailwidget.cc index b73464c6e2..67169e5561 100644 --- a/openpilot/tools/cabana/ui/widgets/detailwidget.cc +++ b/openpilot/tools/cabana/ui/widgets/detailwidget.cc @@ -397,7 +397,7 @@ void CenterWidget::drawWelcomeWidget() { ImGui::SetCursorPos(ImVec2(origin.x + (avail.x - size.x) * 0.5f, y)); ImGui::TextUnformatted(text); }; - ImGui::PushStyleColor(ImGuiCol_Text, colorRgb(169, 169, 169)); + ImGui::PushStyleColor(ImGuiCol_Text, palette().text_disabled); float y = origin.y + avail.y * 0.5f - 90.0f; pushLargeFont(); centered("CABANA", y); diff --git a/openpilot/tools/cabana/ui/widgets/messagebytes.cc b/openpilot/tools/cabana/ui/widgets/messagebytes.cc index 442f5e598f..fb66726b70 100644 --- a/openpilot/tools/cabana/ui/widgets/messagebytes.cc +++ b/openpilot/tools/cabana/ui/widgets/messagebytes.cc @@ -26,9 +26,9 @@ ImVec2 bytesCellSize(int n, bool multiple_lines) { ImU32 cellTextColor(bool selected, bool inactive) { if (selected) { const ImU32 text = ImGui::GetColorU32(palette().text_selected); - return inactive ? withAlpha(text, 100) : text; + return text; } - return ImGui::GetColorU32(inactive ? ImGuiCol_TextDisabled : ImGuiCol_Text); + return ImGui::GetColorU32(inactive ? palette().text_inactive : palette().text); } void drawTextCell(ImDrawList *dl, const ImRect &rect, const std::string &text, bool selected, bool inactive, bool align_right) { @@ -48,14 +48,11 @@ void drawBytesCell(ImDrawList *dl, const ImRect &rect, const std::vectorsize() && (*colors)[i].alpha() > 0) { - if (selected) { - pen = ImGui::GetColorU32(ImGuiCol_Text); - dl->AddRectFilled(r.Min, r.Max, ImGui::GetColorU32(ImGuiCol_WindowBg)); - } - dl->AddRectFilled(r.Min, r.Max, toImU32((*colors)[i])); + // Activity backgrounds use the theme's text, including stale values. + pen = ImGui::GetColorU32(ImGuiCol_Text); + dl->AddRectFilled(r.Min, r.Max, toImU32(byteColor((*colors)[i]))); } drawText(dl, r, utils::hexByte(bytes[i]), pen, font, font_size); } diff --git a/openpilot/tools/cabana/ui/widgets/signalview.cc b/openpilot/tools/cabana/ui/widgets/signalview.cc index 921d4b26ab..6562903935 100644 --- a/openpilot/tools/cabana/ui/widgets/signalview.cc +++ b/openpilot/tools/cabana/ui/widgets/signalview.cc @@ -267,7 +267,7 @@ void SignalView::paintCell(ImDrawList *painter, const ImRect &option_rect, const if (item->type == SignalModel::Item::Sig) { // color label ImRect icon_rect(rect.Min.x, rect.Min.y, rect.Min.x + COLOR_LABEL_WIDTH, rect.Max.y); - painter->AddRectFilled(icon_rect.Min, icon_rect.Max, toImU32(item->sig->color.darker(item->highlight ? 125 : 0)), ImGui::GetStyle().FrameRounding); + painter->AddRectFilled(icon_rect.Min, icon_rect.Max, toImU32(item->highlight ? signalHighlight(item->sig->color) : contrastColor(item->sig->color, {0, 0, 0})), ImGui::GetStyle().FrameRounding); drawText(painter, icon_rect, std::to_string(item->row() + 1).c_str(), item->highlight ? IM_COL32_WHITE : IM_COL32_BLACK, nullptr, LABEL_FONT); @@ -276,7 +276,7 @@ void SignalView::paintCell(ImDrawList *painter, const ImRect &option_rect, const if (item->sig->type != cabana::Signal::Type::Normal) { const std::string indicator = multiplexIndicator(item->sig); ImRect indicator_rect(rect.Min.x, rect.Min.y, rect.Min.x + ImGui::CalcTextSize(indicator.c_str()).x, rect.Max.y); - painter->AddRectFilled(indicator_rect.Min, indicator_rect.Max, IM_COL32(160, 160, 164, 255), ImGui::GetStyle().FrameRounding); + painter->AddRectFilled(indicator_rect.Min, indicator_rect.Max, ImGui::GetColorU32(palette().badge), ImGui::GetStyle().FrameRounding); drawElidedText(painter, indicator_rect, indicator, IM_COL32_WHITE, false); rect.Min.x = indicator_rect.Max.x + h_margin * 2; } diff --git a/openpilot/tools/cabana/ui/widgets/videowidget.cc b/openpilot/tools/cabana/ui/widgets/videowidget.cc index 5c8a9ede3c..37b4348fd3 100644 --- a/openpilot/tools/cabana/ui/widgets/videowidget.cc +++ b/openpilot/tools/cabana/ui/widgets/videowidget.cc @@ -425,7 +425,7 @@ void Slider::paint(double thumbnail_time) { groove_rect.Min.y = std::floor(center_y - groove_height / 2); groove_rect.Max.y = groove_rect.Min.y + groove_height; - p->AddRectFilled(groove_rect.Min, groove_rect.Max, timeline_colors[(int)TimelineType::None], groove_height * 0.5f); + p->AddRectFilled(groove_rect.Min, groove_rect.Max, toImU32(graphicColor(fromImVec4(ImGui::ColorConvertU32ToFloat4(timeline_colors[(int)TimelineType::None])), palette().window)), groove_height * 0.5f); double min = minimum() / factor; double max = maximum() / factor; @@ -444,7 +444,7 @@ void Slider::paint(double thumbnail_time) { if (auto replay = getReplay()) { for (const auto &entry : *replay->getTimeline()) { - fillRange(entry.start_time, entry.end_time, timeline_colors[(int)entry.type]); + fillRange(entry.start_time, entry.end_time, toImU32(graphicColor(fromImVec4(ImGui::ColorConvertU32ToFloat4(timeline_colors[(int)entry.type])), palette().window))); } ImU32 empty_color = ImGui::GetColorU32(ImGuiCol_WindowBg, 160 / 255.0f); @@ -531,8 +531,9 @@ void StreamCameraView::draw(const ImVec2 &size, double thumbnail_time) { const char *text = "PAUSED"; const ImVec2 text_size = font->CalcTextSizeA(POINT_16_FONT_SIZE, FLT_MAX, 0.0f, text); const ImVec2 center = rect().GetCenter(); - p->AddText(font, POINT_16_FONT_SIZE, ImVec2(center.x - text_size.x / 2, center.y - text_size.y / 2), - IM_COL32(200, 200, 200, static_cast(255 * 0.7f)), text); + const ImVec2 pos(center.x - text_size.x / 2, center.y - text_size.y / 2); + p->AddRectFilled(ImVec2(pos.x - 4, pos.y - 2), ImVec2(pos.x + text_size.x + 4, pos.y + text_size.y + 2), ImGui::GetColorU32(palette().badge), ImGui::GetStyle().FrameRounding); + p->AddText(font, POINT_16_FONT_SIZE, pos, ImGui::GetColorU32(palette().text_selected), text); } } @@ -583,13 +584,16 @@ void StreamCameraView::drawTime(ImDrawList *p, const ImRect &rect, double second 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 - p->AddText(font, POINT_10_FONT_SIZE, ImVec2(rect.GetCenter().x - text_size.x / 2, rect.Max.y - THUMBNAIL_MARGIN - text_size.y), - IM_COL32_WHITE, text); + const ImVec2 pos(rect.GetCenter().x - text_size.x / 2, rect.Max.y - THUMBNAIL_MARGIN - text_size.y); + p->AddRectFilled(ImVec2(pos.x - 4, pos.y - 2), ImVec2(pos.x + text_size.x + 4, pos.y + text_size.y + 2), ImGui::GetColorU32(palette().badge), ImGui::GetStyle().FrameRounding); + p->AddText(font, POINT_10_FONT_SIZE, pos, ImGui::GetColorU32(palette().text_selected), text); } void StreamCameraView::drawAlert(ImDrawList *p, const ImRect &rect, const Timeline::Entry &alert, float font_size, float rounding) { const ImU32 pen = IM_COL32_WHITE; - ImU32 color = withAlpha(timeline_colors[int(alert.type)], 128); + // Opaque backing keeps alert text readable over every camera frame. + const auto source = fromImVec4(ImGui::ColorConvertU32ToFloat4(timeline_colors[int(alert.type)])); + const ImU32 color = toImU32(contrastColor(source, fromImVec4(palette().text_selected))); std::string text = alert.text1; if (!alert.text2.empty()) text += "\n" + alert.text2;