mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
164a3a1e30
* cabana: keep chart tooltips below menus * cabana: exclude popup menus from plot hover
145 lines
6.3 KiB
C++
145 lines
6.3 KiB
C++
#define IMGUI_DEFINE_MATH_OPERATORS // ImVec2 arithmetic, must precede imgui.h
|
|
#include "tools/cabana/ui/chart/tiplabel.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
#include "tools/cabana/ui/util.h"
|
|
|
|
ImVec2 TipLabel::layoutLines(ImDrawList *p, const ImVec2 &origin, ImU32 fg) const {
|
|
const float font_size = ImGui::GetFontSize();
|
|
const float line_height = std::ceil(ImGui::GetTextLineHeight() + 2);
|
|
const float marker = std::floor(markerSize());
|
|
const float gap = 8;
|
|
const float width = column_widths_[0] + column_widths_[1] + column_widths_[2] + column_widths_[3] + gap * 3;
|
|
const ImU32 muted = ImGui::GetColorU32(ImGuiCol_TextDisabled);
|
|
float y = std::round(origin.y);
|
|
auto draw = [&](float x, const std::string &text, ImU32 color) {
|
|
if (p) p->AddText(ImVec2(std::round(x), y), color, text.c_str());
|
|
};
|
|
const char *heading = !text_.empty() && !text_[0].has_marker ? text_[0].name.c_str() : "Signal";
|
|
const char *headers[] = {heading, "Value", "Min", "Max"};
|
|
if (compact_) {
|
|
const float right = origin.x + std::max(0.0f, area_.GetWidth() - MARGIN * 2 - 1);
|
|
auto cell = [&](float left, const std::string &text, ImU32 color) {
|
|
if (p) drawElidedText(p, ImRect(ImVec2(left, y), ImVec2(right, y + font_size)), text, color);
|
|
};
|
|
cell(origin.x, heading, muted);
|
|
y += line_height;
|
|
for (const auto &line : text_) {
|
|
if (!line.has_marker) continue;
|
|
if (p) p->AddRectFilled(ImVec2(origin.x, y + (font_size - marker) * 0.5f),
|
|
ImVec2(origin.x + marker, y + (font_size + marker) * 0.5f), line.marker);
|
|
cell(origin.x + marker + 6, line.name, fg);
|
|
y += line_height;
|
|
const std::string *values[] = {&line.value, &line.min, &line.max};
|
|
const float label_width = ImGui::CalcTextSize("Value").x + gap;
|
|
for (int i = 0; i < 3; ++i) {
|
|
draw(origin.x, headers[i + 1], muted);
|
|
pushMonoFont(font_size);
|
|
cell(origin.x + label_width, *values[i], i == 0 ? fg : muted);
|
|
popMonoFont();
|
|
y += line_height;
|
|
}
|
|
}
|
|
return ImVec2(right - origin.x, y - origin.y);
|
|
}
|
|
float x = origin.x;
|
|
for (int i = 0; i < 4; ++i) {
|
|
draw(i ? x + column_widths_[i] - ImGui::CalcTextSize(headers[i]).x : x, headers[i], muted);
|
|
x += column_widths_[i] + gap;
|
|
}
|
|
y += line_height;
|
|
if (p) p->AddLine(ImVec2(origin.x, y - 2), ImVec2(origin.x + width, y - 2), ImGui::GetColorU32(ImGuiCol_Border));
|
|
for (const auto &line : text_) {
|
|
if (!line.has_marker) continue;
|
|
if (p) {
|
|
const ImVec2 marker_pos(std::round(origin.x), std::round(y + (font_size - marker) * 0.5f));
|
|
p->AddRectFilled(marker_pos, marker_pos + ImVec2(marker, marker), line.marker);
|
|
}
|
|
if (p) drawElidedText(p, ImRect(ImVec2(origin.x + marker + 6, y),
|
|
ImVec2(origin.x + column_widths_[0], y + font_size)), line.name, fg);
|
|
x = origin.x + column_widths_[0] + gap;
|
|
pushMonoFont(font_size);
|
|
const std::string *values[] = {&line.value, &line.min, &line.max};
|
|
for (int i = 0; i < 3; ++i) {
|
|
draw(x + column_widths_[i + 1] - ImGui::CalcTextSize(values[i]->c_str()).x, *values[i], i == 0 ? fg : muted);
|
|
x += column_widths_[i + 1] + gap;
|
|
}
|
|
popMonoFont();
|
|
y += line_height;
|
|
}
|
|
return ImVec2(width, y - origin.y);
|
|
}
|
|
|
|
ImVec2 TipLabel::sizeHint() const {
|
|
return layoutLines(nullptr, ImVec2(0, 0), 0) + ImVec2(MARGIN * 2, MARGIN * 2);
|
|
}
|
|
|
|
void TipLabel::showText(const ImVec2 &pt, const std::vector<TipLine> &text, const ImRect &rect) {
|
|
bool same_signals = text.size() == text_.size();
|
|
for (size_t i = 1; same_signals && i < text.size(); ++i) same_signals = text[i].name == text_[i].name;
|
|
if (!same_signals) column_widths_ = {};
|
|
text_ = text;
|
|
anchor_ = pt;
|
|
area_ = rect;
|
|
visible_ = !text_.empty();
|
|
}
|
|
|
|
void TipLabel::updateLayout() {
|
|
// Playback notifications can update the text outside an ImGui window scope.
|
|
// Measure and position it only while the owning chart is being drawn.
|
|
column_widths_[0] = std::max(column_widths_[0], ImGui::CalcTextSize(text_.empty() ? "Signal" : text_[0].name.c_str()).x);
|
|
for (const auto &line : text_) {
|
|
if (line.has_marker) column_widths_[0] = std::max(column_widths_[0], std::ceil(markerSize() + 6 + ImGui::CalcTextSize(line.name.c_str()).x));
|
|
}
|
|
pushMonoFont(ImGui::GetFontSize());
|
|
const float number_width = std::ceil(ImGui::CalcTextSize("-0.00000").x);
|
|
for (int i = 1; i < 4; ++i) column_widths_[i] = std::max(column_widths_[i], number_width);
|
|
for (const auto &line : text_) {
|
|
const std::string *values[] = {&line.value, &line.min, &line.max};
|
|
for (int i = 0; i < 3; ++i) column_widths_[i + 1] = std::max(column_widths_[i + 1], std::ceil(ImGui::CalcTextSize(values[i]->c_str()).x));
|
|
}
|
|
popMonoFont();
|
|
const ImGuiViewport *viewport = ImGui::GetWindowViewport();
|
|
ImRect bounds(viewport->WorkPos, viewport->WorkPos + viewport->WorkSize);
|
|
bounds.ClipWith(area_);
|
|
if (bounds.GetWidth() <= 0 || bounds.GetHeight() <= 0) {
|
|
visible_ = false;
|
|
return;
|
|
}
|
|
area_ = bounds;
|
|
const float numeric_width = column_widths_[1] + column_widths_[2] + column_widths_[3] + 24 + MARGIN * 2 + 1;
|
|
compact_ = bounds.GetWidth() < numeric_width + ImGui::GetFontSize() * 4;
|
|
column_widths_[0] = std::min(column_widths_[0], std::max(40.0f, std::min(ImGui::GetFontSize() * 16, bounds.GetWidth() - numeric_width)));
|
|
if (!text_.empty()) {
|
|
ImVec2 extra(1, 1);
|
|
size_ = sizeHint() + extra;
|
|
ImVec2 tip_pos(anchor_.x + 8, area_.Min.y + 2);
|
|
if (anchor_.x >= area_.GetCenter().x) {
|
|
tip_pos.x = anchor_.x - size_.x - 8;
|
|
}
|
|
tip_pos.x = std::clamp(tip_pos.x, bounds.Min.x, std::max(bounds.Min.x, bounds.Max.x - size_.x));
|
|
tip_pos.y = std::clamp(tip_pos.y, bounds.Min.y, std::max(bounds.Min.y, bounds.Max.y - size_.y));
|
|
pos_ = tip_pos;
|
|
visible_ = true;
|
|
return;
|
|
}
|
|
visible_ = false;
|
|
}
|
|
|
|
void TipLabel::draw(const ImRect &rect) {
|
|
if (!visible_) return;
|
|
area_ = rect;
|
|
updateLayout();
|
|
if (!visible_) return;
|
|
|
|
ImDrawList *p = ImGui::GetWindowDrawList();
|
|
p->PushClipRect(area_.Min, area_.Max, true);
|
|
// filled panel with a 1px frame
|
|
p->AddRectFilled(pos_, pos_ + size_, ImGui::GetColorU32(ImGuiCol_PopupBg), ImGui::GetStyle().PopupRounding);
|
|
p->AddRect(pos_, pos_ + size_, ImGui::GetColorU32(ImGuiCol_Border), ImGui::GetStyle().PopupRounding);
|
|
layoutLines(p, pos_ + ImVec2(MARGIN, MARGIN), ImGui::GetColorU32(ImGuiCol_Text));
|
|
p->PopClipRect();
|
|
}
|