cabana: fix chart ranges and control layout (#38797)

This commit is contained in:
Trey Moen
2026-09-07 12:42:41 -07:00
committed by GitHub
parent 624d5b9995
commit 1bb019521e
7 changed files with 166 additions and 71 deletions
+39 -26
View File
@@ -6,7 +6,6 @@
#include <cmath>
#include <cstdio>
#include <limits>
#include <random>
#include "tools/cabana/core/settings.h"
#include "tools/cabana/settings.h"
@@ -139,7 +138,7 @@ void ChartView::updateLayout() {
const ImVec2 top_left = layout_.rect.Min + ImVec2(LAYOUT_MARGINS.x, LAYOUT_MARGINS.y);
layout_.move_icon_rect = ImRect(top_left, top_left + grip);
const ImVec2 btn_size(iconButtonWidth(), iconButtonWidth());
const ImVec2 close_min(layout_.rect.Max.x - std::max(LAYOUT_MARGINS.z, CONTROL_OUTLINE_PADDING) - btn_size.x, top_left.y);
const ImVec2 close_min(layout_.rect.Max.x - ImGui::GetStyle().WindowPadding.x - btn_size.x, top_left.y);
layout_.close_btn_rect = ImRect(close_min, close_min + btn_size);
const ImVec2 manage_min(close_min.x - btn_size.x - ImGui::GetStyle().ItemInnerSpacing.x, top_left.y);
layout_.manage_btn_rect = ImRect(manage_min, manage_min + btn_size);
@@ -150,7 +149,7 @@ void ChartView::updateLayout() {
const int marker_size = markerSize();
const int row_height = std::max<int>(marker_size, fm_height) + fm_height + 3; // + the signal value line
const int legend_left = layout_.move_icon_rect.Max.x + LEGEND_SPACING;
const int legend_right = std::max<int>(layout_.manage_btn_rect.Min.x - LAYOUT_MARGINS.z, legend_left + 10);
const int legend_right = std::max<int>(layout_.manage_btn_rect.Min.x - ImGui::GetStyle().ItemInnerSpacing.x, legend_left + 10);
// layout legend entries left-to-right, wrapping between the move icon and the buttons
layout_.legend_rects.clear();
@@ -158,6 +157,9 @@ void ChartView::updateLayout() {
for (auto &s : sigs_) {
int w = marker_size + LEGEND_SPACING + bold->CalcTextSizeA(font_size, FLT_MAX, 0.0f, s.sig->name.c_str()).x +
ImGui::CalcTextSize(msgLabel(s.msg_id).c_str()).x;
pushMonoFont(font_size);
w = std::max(w, (int)std::ceil(ImGui::CalcTextSize("-0.00000e+000").x));
popMonoFont();
w = std::min(w, legend_right - legend_left); // keep oversized entries clear of the header buttons
if (x + w > legend_right && x > legend_left) {
x = legend_left;
@@ -262,13 +264,14 @@ void ChartView::updateAxisY() {
auto [first, last] = visibleRange(s.vals);
s.min = std::numeric_limits<double>::max();
s.max = std::numeric_limits<double>::lowest();
if (first == last) continue;
if (can->liveStreaming()) {
for (auto it = first; it != last; ++it) {
if (it->y < s.min) s.min = it->y;
if (it->y > s.max) s.max = it->y;
}
} else {
std::tie(s.min, s.max) = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last));
std::tie(s.min, s.max) = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last) - 1);
}
min = std::min(min, s.min);
max = std::max(max, s.max);
@@ -278,7 +281,8 @@ void ChartView::updateAxisY() {
y_unit_ = unit;
double delta = std::abs(max - min) < 1e-3 ? 1 : (max - min) * 0.05;
const double magnitude = std::max(std::abs(min), std::abs(max));
double delta = max - min <= magnitude * 1e-9 ? (magnitude > 0 ? magnitude * 0.05 : 1) : (max - min) * 0.05;
auto [min_y, max_y, tick_count] = getNiceAxisNumbers(min - delta, max + delta, 3);
if (min_y != y_min_ || max_y != y_max_) {
y_min_ = min_y;
@@ -412,10 +416,10 @@ void ChartView::handleMouseRelease() {
// Prevent zooming/seeking past the end of the route
double min = std::clamp(secondsAtPoint(rubber_rect_.Min), can->minSeconds(), can->maxSeconds());
double max = std::clamp(secondsAtPoint(rubber_rect_.Max), can->minSeconds(), can->maxSeconds());
if (rubber_rect_.GetWidth() <= 0) {
// no rubber dragged, seek to mouse position
if (rubber_rect_.GetWidth() <= 10) {
// Small movements are still clicks; use the same threshold as drag-to-zoom.
can->seekTo(std::clamp(secondsAtPoint(press_pos_), can->minSeconds(), can->maxSeconds()));
} else if (rubber_rect_.GetWidth() > 10 && (max - min) > MIN_ZOOM_SECONDS) {
} else if ((max - min) > MIN_ZOOM_SECONDS) {
charts_widget_->zoom_undo_stack_.push(new ZoomCommand({min, max}));
}
rubber_rect_ = ImRect();
@@ -434,8 +438,8 @@ void ChartView::handleMouseRelease() {
void ChartView::takeSignalsFrom(ChartView *source) {
for (auto &s : source->sigs_) {
s.color = uniqueColor(s.color);
sigs_.push_back(std::move(s));
sigs_.back().color = uniqueColor(sigs_.back().color, sigs_.back().sig);
}
source->sigs_.clear();
updateAxisY();
@@ -478,17 +482,17 @@ void ChartView::showTip(double sec) {
s.track_pt = *pt;
x = std::max(x, xPos(pt->x));
}
std::string name = sigs_.size() > 1 ? s.sig->name + ": " : "";
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, .bold = value, .rest = " (" + min + ", " + max + ")"});
text_list.push_back({.has_marker = true, .marker = toImU32(s.color), .name = name, .value = value, .min = min, .max = max});
}
}
if (x < 0) {
x = tooltip_x_;
}
ImVec2 pt(x, layout_.plot_area.Min.y);
text_list.insert(text_list.begin(), TipLine{.name = formatNumber(secondsAtPoint({x, 0}), 2)});
text_list.insert(text_list.begin(), TipLine{.name = formatNumber(sec, 2) + " s"});
tip_label_.showText(pt, text_list, visible_rect);
}
@@ -747,6 +751,7 @@ void ChartView::drawTimeline() {
}
void ChartView::drawSignalValue() {
pushMonoFont(ImGui::GetFontSize());
ImDrawList *painter = ImGui::GetWindowDrawList();
const ImU32 color = ImGui::GetColorU32(ImGuiCol_Text);
for (int i = 0; i < sigs_.size() && i < layout_.legend_rects.size(); ++i) {
@@ -757,27 +762,35 @@ void ChartView::drawSignalValue() {
ImRect value_rect(value_min, value_min + layout_.legend_rects[i].GetSize());
float w = ImGui::CalcTextSize(value.c_str()).x;
if (w <= value_rect.GetWidth()) {
painter->AddText(ImVec2(value_rect.GetCenter().x - w / 2, value_rect.Min.y), color, value.c_str());
painter->AddText(value_rect.Min, color, value.c_str());
} else {
addTextEllipsis(painter, ImGui::GetFont(), color, value_rect.Min, value_rect.Max.x, value);
}
}
popMonoFont();
}
CabanaColor ChartView::uniqueColor(CabanaColor color, const cabana::Signal *exclude) const {
for (auto &s : sigs_) {
if (s.sig != exclude && std::abs(color.hsv().hue - s.color.hsv().hue) < 0.1) {
// use different color to distinguish it from others.
auto last_color = sigs_.back().color;
static thread_local std::mt19937 rng{std::random_device{}()};
std::uniform_int_distribution<int> sat(35, 99);
std::uniform_int_distribution<int> val(85, 99);
color = CabanaColor::fromHsv(std::fmod(last_color.hsv().hue + 60 / 360.0, 1.0),
sat(rng) / 100.0,
val(rng) / 100.0,
color.a / 255.0f);
break;
auto separation = [&](float hue) {
float distance = 1.0f;
for (const auto &s : sigs_) {
if (exclude && s.sig == exclude) continue;
const float delta = std::abs(hue - s.color.hsv().hue);
distance = std::min(distance, std::min(delta, 1.0f - delta));
}
return distance;
};
const float original_hue = color.hsv().hue;
if (separation(original_hue) >= 0.1f) return color;
float best_hue = original_hue, best_distance = -1;
for (int i = 0; i < 36; ++i) {
const float hue = std::fmod(original_hue + i / 36.0f, 1.0f);
const float distance = separation(hue);
if (distance > best_distance) {
best_hue = hue;
best_distance = distance;
}
}
return color;
return CabanaColor::fromHsv(best_hue, 0.8f, 0.9f, color.a / 255.0f);
}
+1 -1
View File
@@ -120,7 +120,7 @@ private:
double y_min_ = 0;
double y_max_ = 1;
int y_tick_count_ = 3;
int y_precision_ = 0;
int y_precision_ = 1;
std::string y_unit_;
// interaction
enum class MouseMode { None, Rubber, Scrub };
+27 -13
View File
@@ -206,12 +206,13 @@ void ChartsWidget::drawToolBar() {
const std::string range_lb = is_zoomed ? std::string() : utils::formatSeconds(max_chart_range_);
std::string reset_zoom_text;
if (!is_zoomed) {
items.push_back({ImGui::CalcTextSize(range_lb.c_str()).x, [&range_lb]() {
// the range label and the slider are one unit: drawn inline and moved to the overflow menu together
slider_index = items.size();
const float label_width = ImGui::CalcTextSize(range_lb.c_str()).x + ImGui::GetStyle().ItemInnerSpacing.x;
items.push_back({label_width + slider_width, [this, &range_lb, &slider_width]() {
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(range_lb.c_str());
}});
slider_index = items.size();
items.push_back({slider_width, [this, &slider_width]() {
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
// Restore the slider width in overflow; the toolbar may have shrunk it.
const bool in_menu = ImGui::GetCurrentWindow()->Flags & ImGuiWindowFlags_Popup;
const float width = in_menu ? std::max(ImGui::GetContentRegionAvail().x, 150.0f) : slider_width;
@@ -219,9 +220,17 @@ void ChartsWidget::drawToolBar() {
ImGui::SetItemTooltip("Set the chart range");
}});
} else {
const auto &range = *can->timeRange();
char buf[64];
snprintf(buf, sizeof(buf), "%.2f-%.2f", can->timeRange()->first, can->timeRange()->second);
snprintf(buf, sizeof(buf), "%.2f-%.2f", range.first, range.second);
reset_zoom_text = buf;
// The undo/redo/reset buttons form one group. The reset button has a fixed width in the mono font,
// sized for the longest range the stream can show, so its neighbors do not shift as the range changes.
const int digits = std::max({1, (int)std::to_string((long long)can->maxSeconds()).size(), (int)std::to_string((long long)range.second).size()});
const std::string widest = std::string(digits, '0') + ".00";
pushMonoFont(ImGui::GetFontSize());
const float reset_zoom_width = iconTextButtonWidth(icon::ZOOM_OUT, widest + "-" + widest);
popMonoFont();
items.push_back({iconButtonWidth(), [this]() {
ImGui::BeginDisabled(!zoom_undo_stack_.canUndo());
if (iconButton("undo_zoom", icon::ARROW_COUNTERCLOCKWISE, "Undo Zoom")) zoom_undo_stack_.undo();
@@ -232,10 +241,15 @@ void ChartsWidget::drawToolBar() {
if (iconButton("redo_zoom", icon::ARROW_CLOCKWISE, "Redo Zoom")) zoom_undo_stack_.redo();
ImGui::EndDisabled();
}});
items.push_back({toolbarButtonWidth(std::string(icon::ZOOM_OUT) + " " + reset_zoom_text), [this, &reset_zoom_text]() {
if (ImGui::Button((std::string(icon::ZOOM_OUT) + " " + reset_zoom_text + "###reset_zoom_btn").c_str())) zoomReset();
items.back().tight = true;
items.push_back({reset_zoom_width, [this, &reset_zoom_text, reset_zoom_width]() {
pushMonoFont(ImGui::GetFontSize());
const bool clicked = iconTextButton("reset_zoom_btn", icon::ZOOM_OUT, reset_zoom_text, reset_zoom_width);
popMonoFont();
if (clicked) zoomReset();
ImGui::SetItemTooltip("Reset Zoom");
}});
items.back().tight = true;
}
items.push_back(toolbarAction("remove_all_btn", icon::TRASH, "Remove all charts", [this]() { removeAll(); }, !charts_.empty()));
const char *dock_btn_icon = is_docked_ ? icon::BOX_ARROW_UP_RIGHT : icon::BOX_ARROW_IN_DOWN_LEFT;
@@ -247,7 +261,7 @@ void ChartsWidget::drawToolBar() {
const float shrink = std::min(slider_width - MIN_RANGE_SLIDER_WIDTH, toolbarWidth(items, spacer_index) - ImGui::GetContentRegionAvail().x);
if (shrink > 0.0f) {
slider_width -= shrink;
items[slider_index].width = slider_width;
items[slider_index].width -= shrink;
}
}
drawToolbar(items, spacer_index);
@@ -273,7 +287,8 @@ ChartView *ChartsWidget::createChart(int pos) {
ChartView *ptr = chart.get();
pos = std::clamp(pos, 0, (int)charts_.size());
charts_.insert(charts_.begin() + pos, std::move(chart));
currentCharts().insert(currentCharts().begin() + pos, ptr);
auto &current = currentCharts();
current.insert(current.begin() + std::min(pos, (int)current.size()), ptr);
updateLayout();
return ptr;
}
@@ -291,8 +306,8 @@ void ChartsWidget::showChart(const MessageId &id, const cabana::Signal *sig, boo
void ChartsWidget::splitChart(ChartView *src_chart) {
if (src_chart->signals().size() > 1) {
auto it = std::find_if(charts_.begin(), charts_.end(), [src_chart](auto &c) { return c.get() == src_chart; });
const int pos = it - charts_.begin() + 1;
auto &current = currentCharts();
const int pos = std::find(current.begin(), current.end(), src_chart) - current.begin() + 1;
for (auto &s : src_chart->takeExtraSignals()) {
createChart(pos)->adoptSignal(std::move(s));
}
@@ -606,9 +621,8 @@ void ChartsWidget::draw() {
}
void ChartsContainer::draw() {
ImGuiWindow *window = ImGui::GetCurrentWindow();
const ImVec2 start = ImGui::GetCursorScreenPos();
const float width_avail = window->InnerRect.GetWidth() - (window->ScrollbarY ? ImGui::GetStyle().ItemInnerSpacing.x : 0.0f);
const float width_avail = ImGui::GetContentRegionAvail().x;
geometry_ = ImRect(start, start + ImVec2(width_avail, 0));
charts_widget_->updateLayout();
+69 -27
View File
@@ -2,35 +2,49 @@
#include "tools/cabana/ui/chart/tiplabel.h"
#include <algorithm>
#include <cfloat>
#include <cmath>
#include "tools/cabana/ui/util.h"
ImVec2 TipLabel::layoutLines(ImDrawList *p, const ImVec2 &origin, ImU32 fg) const {
ImFont *bold = boldFont();
const float font_size = ImGui::GetFontSize();
const float line_height = ImGui::GetTextLineHeight();
ImVec2 size(0, 0);
float y = origin.y;
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"};
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_) {
float x = origin.x;
if (line.has_marker) {
if (p) drawColorMarker(p, ImVec2(x, y), line.marker);
x += markerSize() + 4;
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) p->AddText(ImVec2(x, y), fg, line.name.c_str());
x += ImGui::CalcTextSize(line.name.c_str()).x;
if (!line.bold.empty()) {
if (p) p->AddText(bold, font_size, ImVec2(x, y), fg, line.bold.c_str());
x += bold->CalcTextSizeA(font_size, FLT_MAX, 0.0f, line.bold.c_str()).x;
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;
}
if (p) p->AddText(ImVec2(x, y), fg, line.rest.c_str());
x += ImGui::CalcTextSize(line.rest.c_str()).x;
size.x = std::max(size.x, x - origin.x);
popMonoFont();
y += line_height;
}
size.y = y - origin.y;
return size;
return ImVec2(width, y - origin.y);
}
ImVec2 TipLabel::sizeHint() const {
@@ -38,25 +52,53 @@ ImVec2 TipLabel::sizeHint() const {
}
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();
const ImRect bounds(viewport->WorkPos, viewport->WorkPos + viewport->WorkSize);
const float numeric_width = column_widths_[1] + column_widths_[2] + column_widths_[3] + 24 + MARGIN * 2 + 1;
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(pt.x + 8, rect.Min.y + 2);
if (tip_pos.x + size_.x >= rect.Max.x) {
tip_pos.x = pt.x - size_.x - 8;
}
if (rect.Contains(ImRect(tip_pos, tip_pos + size_))) {
pos_ = tip_pos;
visible_ = true;
return;
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() {
if (!visible_) return;
updateLayout();
ImDrawList *p = ImGui::GetForegroundDrawList();
// filled panel with a 1px frame
+8 -4
View File
@@ -1,18 +1,18 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include "imgui.h"
#include "imgui_internal.h"
// one line of the tip: [square] name <b>value</b> (min, max)
// A signal row, or a time heading when has_marker is false.
struct TipLine {
bool has_marker = false;
ImU32 marker = 0;
std::string name;
std::string bold;
std::string rest;
std::string value, min, max;
};
class TipLabel {
@@ -26,9 +26,13 @@ private:
// lays the lines out from origin, drawing them when p is given; returns the size of the text block
ImVec2 layoutLines(ImDrawList *p, const ImVec2 &origin, ImU32 fg) const;
ImVec2 sizeHint() const;
void updateLayout();
static constexpr float MARGIN = 2.0f; // 1 + PM_ToolTipLabelFrameWidth
static constexpr float MARGIN = 6.0f;
std::vector<TipLine> text_;
std::array<float, 4> column_widths_{};
ImVec2 anchor_;
ImRect area_;
ImVec2 pos_;
ImVec2 size_;
bool visible_ = false;
+20
View File
@@ -214,6 +214,26 @@ bool iconButton(const char *id, const char *icon, const char *tooltip) {
return clicked;
}
float iconTextButtonWidth(const char *icon, const std::string &text) {
const ImGuiStyle &style = ImGui::GetStyle();
return ImGui::CalcTextSize(icon).x + style.ItemInnerSpacing.x + ImGui::CalcTextSize(text.c_str(), nullptr, true).x + style.FramePadding.x * 2;
}
bool iconTextButton(const char *id, const char *icon, const std::string &text, float width) {
const ImGuiStyle &style = ImGui::GetStyle();
if (width <= 0.0f) width = iconTextButtonWidth(icon, text);
const bool clicked = ImGui::Button((std::string("###") + id).c_str(), ImVec2(width, 0.0f));
const ImVec2 min = ImGui::GetItemRectMin(), max = ImGui::GetItemRectMax();
const ImU32 color = ImGui::GetColorU32(ImGuiCol_Text);
auto *draw_list = ImGui::GetWindowDrawList();
draw_list->AddText(ImVec2(min.x + style.FramePadding.x, min.y + style.FramePadding.y), color, icon);
// the text is centered between the icon and the right padding
const float left = min.x + style.FramePadding.x + ImGui::CalcTextSize(icon).x + style.ItemInnerSpacing.x;
const float slack = max.x - style.FramePadding.x - left - ImGui::CalcTextSize(text.c_str(), nullptr, true).x;
draw_list->AddText(ImVec2(left + std::max(0.0f, slack * 0.5f), min.y + style.FramePadding.y), color, text.c_str());
return clicked;
}
void disabledItemTooltip(const char *text) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", text);
}
+2
View File
@@ -74,6 +74,8 @@ int nonWhitespaceValidator(ImGuiInputTextCallbackData *data);
// Use ItemInnerSpacing between related buttons and ItemSpacing between groups.
bool iconButton(const char *id, const char *icon, const char *tooltip = nullptr);
float iconButtonWidth();
bool iconTextButton(const char *id, const char *icon, const std::string &text, float width = 0.0f);
float iconTextButtonWidth(const char *icon, const std::string &text);
// tooltip for the last item that also shows while the item is disabled
void disabledItemTooltip(const char *text);