cabana: improve theme contrast (#38965)

* cabana: centralize accessible theme and data colors

* cabana: remove color contrast tests

* cabana: simplify theme color helpers

* cabana: soften inactive message rows in dark mode

* cabana: retain original signal tooltip behavior
This commit is contained in:
Trey Moen
2026-09-19 09:47:49 -07:00
committed by GitHub
parent 5ae0da0e46
commit 2eee697f7d
12 changed files with 150 additions and 65 deletions
+7 -7
View File
@@ -475,7 +475,7 @@ void ChartView::showTip(double sec) {
std::string name = s.sig->name;
std::string min = s.min == std::numeric_limits<double>::max() ? "--" : utils::toString(s.min);
std::string max = s.max == std::numeric_limits<double>::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);
}
+3 -2
View File
@@ -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
+1 -1
View File
@@ -26,7 +26,7 @@ private:
std::vector<Point> points_;
std::vector<ImVec2> 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;
+3 -1
View File
@@ -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 {
+87 -14
View File
@@ -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<uint8_t>(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<uint8_t>(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; }
+12 -2
View File
@@ -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<uint8_t>(std::lround(c.x * 255)), static_cast<uint8_t>(std::lround(c.y * 255)),
static_cast<uint8_t>(std::lround(c.z * 255)), static_cast<uint8_t>(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();
+3 -2
View File
@@ -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;
+15 -18
View File
@@ -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);
@@ -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);
@@ -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::vector<uint8_t
const ImVec2 min(rect.Min.x + column * byte_size.x, rect.Min.y + row * byte_size.y);
const ImRect r(min, ImVec2(min.x + byte_size.x, min.y + byte_size.y));
// a colored unselected byte keeps text_pen, like the other cells of the row
ImU32 pen = text_pen;
if (colors && i < (int)colors->size() && (*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);
}
@@ -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;
}
@@ -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<int>(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;