mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
cabana: refine signal heatmap grid (#38964)
* cabana: refine signal heatmap grid * cabana: restore direct signal color blending * cabana: strengthen heatmap signal saturation * Revert "cabana: strengthen heatmap signal saturation" This reverts commit 07ee9664d533f260b5bbfaed1afc4391c795603c. * cabana: remove signal cell gradients
This commit is contained in:
@@ -208,8 +208,10 @@ CabanaColor byteColor(const CabanaColor &color) {
|
||||
|
||||
CabanaColor signalFill(const CabanaColor &color, bool defined) {
|
||||
const bool dark = palette().text.x > 0.5f;
|
||||
// Constrain the full-strength color before blending to preserve the activity ramp.
|
||||
if (defined) return composite(boundLuminance(color, 0.18, !dark), fromImVec4(palette().surface));
|
||||
auto fill = composite(color, fromImVec4(palette().surface));
|
||||
fill = composite(dark ? CabanaColor(0, 0, 0, 115) : CabanaColor(255, 255, 255, defined ? 20 : 40), fill);
|
||||
fill = composite(dark ? CabanaColor(0, 0, 0, 115) : CabanaColor(255, 255, 255, 40), fill);
|
||||
return boundLuminance(fill, 0.18, !dark); // 4.5:1 with the theme's text
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace {
|
||||
const int CELL_HEIGHT = 36;
|
||||
const float CELL_FONT_SIZE = UI_FONT_SIZE + 2.0f;
|
||||
const float SMALL_FONT_SIZE = 10.0f; // Inter needs 10 px for a 7 px cap height
|
||||
const int VERTICAL_HEADER_WIDTH = 30;
|
||||
const int GRID_COLUMN_COUNT = BinaryView::COLUMN_COUNT + 1;
|
||||
inline int get_bit_pos(const BinaryIndex &index) { return flipBitPos(index.row * 8 + index.column); }
|
||||
|
||||
inline ImU32 paletteBase() { return ImGui::GetColorU32(ImGuiCol_ChildBg); }
|
||||
@@ -126,7 +126,7 @@ ImVec2 BinaryView::minimumSizeHint() const {
|
||||
pushMonoFont(CELL_FONT_SIZE);
|
||||
const float min_section_size = std::ceil(ImGui::CalcTextSize("FF").x) + 10.0f;
|
||||
popMonoFont();
|
||||
return {(min_section_size + 1) * 9 + VERTICAL_HEADER_WIDTH + 2,
|
||||
return {min_section_size * GRID_COLUMN_COUNT + 2,
|
||||
static_cast<float>(CELL_HEIGHT * std::min(row_count_, 10) + 2)};
|
||||
}
|
||||
|
||||
@@ -173,6 +173,8 @@ void BinaryView::highlightPosition(const ImVec2 &pos) {
|
||||
auto item = &cellAt(index);
|
||||
const cabana::Signal *sig = item->sigs.empty() ? nullptr : item->sigs.back();
|
||||
highlight(sig);
|
||||
} else {
|
||||
highlight(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,18 +294,18 @@ std::tuple<int, int, bool> BinaryView::getSelection(BinaryIndex index) {
|
||||
}
|
||||
|
||||
BinaryIndex BinaryView::indexAt(const ImVec2 &pos) const {
|
||||
if (column_width_ <= 0 || pos.x < grid_pos_.x + VERTICAL_HEADER_WIDTH || pos.y < grid_pos_.y) return {};
|
||||
int column = static_cast<int>((pos.x - grid_pos_.x - VERTICAL_HEADER_WIDTH) / column_width_);
|
||||
if (column_width_ <= 0 || pos.x < grid_pos_.x + IM_ROUND(column_width_) || pos.y < grid_pos_.y) return {};
|
||||
int column = 0;
|
||||
while (column < COLUMN_COUNT && pos.x >= grid_pos_.x + IM_ROUND((column + 2) * column_width_)) ++column;
|
||||
int row = static_cast<int>((pos.y - grid_pos_.y) / CELL_HEIGHT);
|
||||
if (column >= COLUMN_COUNT || row >= row_count_) return {};
|
||||
return {row, column};
|
||||
}
|
||||
|
||||
ImRect BinaryView::visualRect(const BinaryIndex &index) const {
|
||||
// sections are integral: round the edges so neighboring cells share them exactly and the cells are
|
||||
// painted edge to edge with no grid line between them
|
||||
const float x0 = grid_pos_.x + VERTICAL_HEADER_WIDTH + IM_ROUND(index.column * column_width_);
|
||||
const float x1 = grid_pos_.x + VERTICAL_HEADER_WIDTH + IM_ROUND((index.column + 1) * column_width_);
|
||||
// Round shared edges consistently across the byte index, bits, and hex column.
|
||||
const float x0 = grid_pos_.x + IM_ROUND((index.column + 1) * column_width_);
|
||||
const float x1 = grid_pos_.x + IM_ROUND((index.column + 2) * column_width_);
|
||||
const float y = grid_pos_.y + index.row * CELL_HEIGHT;
|
||||
return ImRect(x0, y, x1, y + CELL_HEIGHT);
|
||||
}
|
||||
@@ -320,17 +322,16 @@ void BinaryView::draw() {
|
||||
pushMonoFont(CELL_FONT_SIZE);
|
||||
const float min_column_width = std::ceil(ImGui::CalcTextSize("FF").x) + 10.0f;
|
||||
popMonoFont();
|
||||
const float width = std::max(ImGui::GetContentRegionAvail().x, VERTICAL_HEADER_WIDTH + min_column_width * COLUMN_COUNT);
|
||||
column_width_ = std::max(min_column_width, (width - VERTICAL_HEADER_WIDTH) / COLUMN_COUNT);
|
||||
const float width = std::max(ImGui::GetContentRegionAvail().x, min_column_width * GRID_COLUMN_COUNT);
|
||||
column_width_ = std::max(min_column_width, width / GRID_COLUMN_COUNT);
|
||||
grid_pos_ = ImGui::GetCursorScreenPos();
|
||||
ImGui::InvisibleButton("##binary_view", ImVec2(std::max(width, 1.0f), std::max(static_cast<float>(rows * CELL_HEIGHT), 1.0f)));
|
||||
ImDrawList *painter = ImGui::GetWindowDrawList();
|
||||
painter->AddRectFilled(grid_pos_, ImVec2(grid_pos_.x + width, grid_pos_.y + rows * CELL_HEIGHT), paletteBase());
|
||||
|
||||
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());
|
||||
const ImRect r(grid_pos_.x, grid_pos_.y + row * CELL_HEIGHT, grid_pos_.x + IM_ROUND(column_width_), grid_pos_.y + (row + 1) * CELL_HEIGHT);
|
||||
drawText(painter, r, std::to_string(row).c_str(), paletteText(), nullptr, CELL_FONT_SIZE);
|
||||
}
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
for (int column = 0; column < COLUMN_COUNT; ++column) {
|
||||
@@ -339,6 +340,29 @@ void BinaryView::draw() {
|
||||
}
|
||||
}
|
||||
|
||||
// One consistent grid over every cell; hover never changes its geometry.
|
||||
const ImU32 grid_color = ImGui::GetColorU32(ImGuiCol_Border, 0.35f);
|
||||
const float right = grid_pos_.x + IM_ROUND(GRID_COLUMN_COUNT * column_width_);
|
||||
const float bottom = grid_pos_.y + rows * CELL_HEIGHT;
|
||||
for (int column = 0; column <= GRID_COLUMN_COUNT; ++column) {
|
||||
const float x = std::clamp(grid_pos_.x + IM_ROUND(column * column_width_), grid_pos_.x + 0.5f, right - 0.5f);
|
||||
painter->AddLine(ImVec2(x, grid_pos_.y), ImVec2(x, bottom), grid_color);
|
||||
}
|
||||
for (int row = 0; row <= rows; ++row) {
|
||||
const float y = grid_pos_.y + row * CELL_HEIGHT;
|
||||
painter->AddLine(ImVec2(grid_pos_.x, y), ImVec2(right, y), grid_color);
|
||||
}
|
||||
|
||||
// Signal definitions are a separate layer from heatmap activity.
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
for (int column = 0; column < HEX_COLUMN; ++column) {
|
||||
const BinaryIndex index{row, column};
|
||||
for (const auto *sig : cellAt(index).sigs) {
|
||||
if (!hasSelection() || sig != resize_sig_) drawSignalOutline(painter, visualRect(index), index, sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ImVec2 mouse = ImGui::GetMousePos();
|
||||
const bool hovered = ImGui::IsItemHovered();
|
||||
const bool active = ImGui::IsItemActive();
|
||||
@@ -443,19 +467,12 @@ const std::vector<std::array<uint32_t, 8>> &BinaryView::bitFlipChanges(size_t ms
|
||||
return bit_flip_tracker_.counts.bits;
|
||||
}
|
||||
|
||||
bool BinaryView::hasSignal(const BinaryIndex &index, int dx, int dy, const cabana::Signal *sig) const {
|
||||
if (!index.isValid()) return false;
|
||||
int idx = (index.row + dy) * COLUMN_COUNT + index.column + dx;
|
||||
if (idx < 0 || idx >= (int)cells_.size()) return false;
|
||||
auto &s = cells_[idx].sigs;
|
||||
return std::find(s.begin(), s.end(), sig) != s.end();
|
||||
}
|
||||
|
||||
void BinaryView::paintCell(ImDrawList *painter, const ImRect &rect, const BinaryIndex &index) const {
|
||||
auto item = &cellAt(index);
|
||||
ImFont *font = ImGui::GetFont();
|
||||
float font_size = CELL_FONT_SIZE;
|
||||
ImU32 pen = paletteText();
|
||||
const bool hovered = hovered_sig_ && std::find(item->sigs.begin(), item->sigs.end(), hovered_sig_) != item->sigs.end();
|
||||
|
||||
if (index.column == HEX_COLUMN) {
|
||||
if (item->valid) {
|
||||
@@ -473,12 +490,12 @@ void BinaryView::paintCell(ImDrawList *painter, const ImRect &rect, const Binary
|
||||
for (auto &s : item->sigs) {
|
||||
drawSignalCell(painter, rect, index, s);
|
||||
}
|
||||
// Hover covers the entire signal, including bits shared with another definition.
|
||||
if (hovered) painter->AddRectFilled(rect.Min, rect.Max, toImU32(signalHighlight(hovered_sig_->color)));
|
||||
} else if (item->valid) {
|
||||
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();
|
||||
if (bright) painter->AddRectFilled(rect.Min, rect.Max, toImU32(signalHighlight(hovered_sig_->color)));
|
||||
pen = bright ? IM_COL32_WHITE : paletteText();
|
||||
pen = hovered ? IM_COL32_WHITE : paletteText();
|
||||
}
|
||||
|
||||
const auto text = fromImVec4(ImGui::ColorConvertU32ToFloat4(pen));
|
||||
@@ -488,6 +505,7 @@ void BinaryView::paintCell(ImDrawList *painter, const ImRect &rect, const Binary
|
||||
} else if (!item->valid) {
|
||||
fillBDiagPattern(painter, rect, pattern);
|
||||
}
|
||||
|
||||
if (item->valid) {
|
||||
if (index.column == HEX_COLUMN) {
|
||||
drawBoldText(painter, rect, utils::hexByte(item->val), pen, font, font_size);
|
||||
@@ -501,64 +519,38 @@ void BinaryView::paintCell(ImDrawList *painter, const ImRect &rect, const Binary
|
||||
}
|
||||
}
|
||||
|
||||
// Draw border on edge of signal
|
||||
void BinaryView::drawSignalCell(ImDrawList *painter, const ImRect &rect, const BinaryIndex &index, const cabana::Signal *sig) const {
|
||||
bool draw_left = !hasSignal(index, -1, 0, sig);
|
||||
bool draw_top = !hasSignal(index, 0, -1, sig);
|
||||
bool draw_right = !hasSignal(index, 1, 0, sig);
|
||||
bool draw_bottom = !hasSignal(index, 0, 1, sig);
|
||||
bool BinaryView::hasSignal(const BinaryIndex &index, int dx, int dy, const cabana::Signal *sig) const {
|
||||
const int row = index.row + dy;
|
||||
const int column = index.column + dx;
|
||||
if (row < 0 || row >= row_count_ || column < 0 || column >= HEX_COLUMN) return false;
|
||||
const auto &signals = cellAt({row, column}).sigs;
|
||||
return std::find(signals.begin(), signals.end(), sig) != signals.end();
|
||||
}
|
||||
|
||||
const int spacing = 2;
|
||||
ImRect rc(rect.Min.x + draw_left * 3, rect.Min.y + draw_top * spacing, rect.Max.x - draw_right * 3, rect.Max.y - draw_bottom * spacing);
|
||||
std::vector<ImRect> subtract;
|
||||
if (!draw_top) {
|
||||
if (!draw_left && !hasSignal(index, -1, -1, sig)) {
|
||||
subtract.emplace_back(rc.Min.x, rc.Min.y, rc.Min.x + 3, rc.Min.y + spacing);
|
||||
} else if (!draw_right && !hasSignal(index, 1, -1, sig)) {
|
||||
subtract.emplace_back(rc.Max.x - 3, rc.Min.y, rc.Max.x, rc.Min.y + spacing);
|
||||
}
|
||||
}
|
||||
if (!draw_bottom) {
|
||||
if (!draw_left && !hasSignal(index, -1, 1, sig)) {
|
||||
subtract.emplace_back(rc.Min.x, rc.Max.y - spacing, rc.Min.x + 3, rc.Max.y);
|
||||
} else if (!draw_right && !hasSignal(index, 1, 1, sig)) {
|
||||
subtract.emplace_back(rc.Max.x - 3, rc.Max.y - spacing, rc.Max.x, rc.Max.y);
|
||||
}
|
||||
}
|
||||
// rc split into horizontal bands with the notch corners removed: at most one notch in the top band and
|
||||
// one in the bottom band
|
||||
const ImRect *top_notch = !subtract.empty() && subtract.front().Min.y == rc.Min.y ? &subtract.front() : nullptr;
|
||||
const ImRect *bottom_notch = !subtract.empty() && subtract.back().Min.y != rc.Min.y ? &subtract.back() : nullptr;
|
||||
std::vector<ImRect> region;
|
||||
auto band = [&](const ImRect *notch, float y0, float y1) {
|
||||
const float x0 = notch && notch->Min.x == rc.Min.x ? notch->Max.x : rc.Min.x;
|
||||
const float x1 = notch && notch->Min.x != rc.Min.x ? notch->Min.x : rc.Max.x;
|
||||
if (x1 > x0 && y1 > y0) region.emplace_back(x0, y0, x1, y1);
|
||||
void BinaryView::drawSignalOutline(ImDrawList *painter, const ImRect &rect, const BinaryIndex &index,
|
||||
const cabana::Signal *sig) const {
|
||||
// Definition boundaries remain visible even at zero activity. Keep them above the
|
||||
// fills and grid, at the same fixed position before and during hover.
|
||||
const ImU32 edge = toImU32(signalOutline(sig->color, sig == hovered_sig_));
|
||||
const bool left = !hasSignal(index, -1, 0, sig);
|
||||
const bool right = !hasSignal(index, 1, 0, sig);
|
||||
const bool top = !hasSignal(index, 0, -1, sig);
|
||||
const bool bottom = !hasSignal(index, 0, 1, sig);
|
||||
// Extend one pixel into adjacent cells to join inward corners.
|
||||
auto offset = [&](bool boundary, int dx, int dy) {
|
||||
return boundary ? 1.0f : hasSignal(index, dx, dy, sig) ? -1.0f : 0.0f;
|
||||
};
|
||||
if (top_notch) band(top_notch, rc.Min.y, rc.Min.y + spacing);
|
||||
band(nullptr, rc.Min.y + (top_notch ? spacing : 0), rc.Max.y - (bottom_notch ? spacing : 0));
|
||||
if (bottom_notch) band(bottom_notch, rc.Max.y - spacing, rc.Max.y);
|
||||
const ImVec2 min = rect.Min, max = rect.Max;
|
||||
if (left) painter->AddLine(ImVec2(min.x + 1, min.y + offset(top, -1, -1)), ImVec2(min.x + 1, max.y - offset(bottom, -1, 1)), edge);
|
||||
if (right) painter->AddLine(ImVec2(max.x - 1, min.y + offset(top, 1, -1)), ImVec2(max.x - 1, max.y - offset(bottom, 1, 1)), edge);
|
||||
if (top) painter->AddLine(ImVec2(min.x + offset(left, -1, -1), min.y + 1), ImVec2(max.x - offset(right, 1, -1), min.y + 1), edge);
|
||||
if (bottom) painter->AddLine(ImVec2(min.x + offset(left, -1, 1), max.y - 1), ImVec2(max.x - offset(right, 1, 1), max.y - 1), edge);
|
||||
}
|
||||
|
||||
auto item = &cellAt(index);
|
||||
// Signal intensity and hover share the same full-cell geometry.
|
||||
void BinaryView::drawSignalCell(ImDrawList *painter, const ImRect &rect, const BinaryIndex &index, const cabana::Signal *sig) const {
|
||||
CabanaColor color = sig->color;
|
||||
color.a = 96 + item->bg_color.alpha() * (255 - 96) / 255;
|
||||
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(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);
|
||||
if (draw_bottom) painter->AddLine(ImVec2(rc.Min.x, rc.Max.y - 0.5f), ImVec2(rc.Max.x, rc.Max.y - 0.5f), edge, 1.0f);
|
||||
if (draw_top) painter->AddLine(ImVec2(rc.Min.x, rc.Min.y + 0.5f), ImVec2(rc.Max.x, rc.Min.y + 0.5f), edge, 1.0f);
|
||||
|
||||
// fill gaps inside corners: the 2px stroke is clipped to the region, only the half outside the notch is painted
|
||||
for (auto &r : subtract) {
|
||||
painter->AddRect(r.Min, r.Max, edge, 0.0f, 0, 2.0f);
|
||||
}
|
||||
painter->PopClipRect();
|
||||
}
|
||||
color.a = 96 + cellAt(index).bg_color.alpha() * (255 - 96) / 255;
|
||||
painter->AddRectFilled(rect.Min, rect.Max, paletteBase());
|
||||
painter->AddRectFilled(rect.Min, rect.Max, toImU32(signalFill(color)));
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ private:
|
||||
|
||||
void paintCell(ImDrawList *painter, const ImRect &rect, const BinaryIndex &index) const;
|
||||
bool hasSignal(const BinaryIndex &index, int dx, int dy, const cabana::Signal *sig) const;
|
||||
void drawSignalOutline(ImDrawList *painter, const ImRect &rect, const BinaryIndex &index, const cabana::Signal *sig) const;
|
||||
void drawSignalCell(ImDrawList *painter, const ImRect &rect, const BinaryIndex &index, const cabana::Signal *sig) const;
|
||||
|
||||
MessageId msg_id_;
|
||||
|
||||
Reference in New Issue
Block a user