mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-18 10:23:43 +08:00
cabana: standardize button spacing (#38873)
* cabana: standardize button spacing and control alignment * cabana: center shared step controls and inset chart content * cabana: use uniform per-chart content insets
This commit is contained in:
@@ -15,12 +15,9 @@
|
||||
#include "tools/cabana/ui/util.h"
|
||||
#include "tools/cabana/utils/strings.h"
|
||||
|
||||
const int AXIS_X_TOP_MARGIN = 4;
|
||||
const int X_TICK_COUNT = 5;
|
||||
const double MIN_ZOOM_SECONDS = 0.01; // 10ms
|
||||
const double EPSILON = 1e-6;
|
||||
constexpr ImVec4 LAYOUT_MARGINS{0, 6, 0, 6}; // left, top, right, bottom
|
||||
constexpr int LEGEND_SPACING = 5;
|
||||
static inline bool xLessThan(const ImPlotPoint &p, double x) { return p.x < (x - EPSILON); }
|
||||
static inline bool isNull(const ImPlotPoint &p) { return p.x == 0 && p.y == 0; }
|
||||
|
||||
@@ -135,27 +132,32 @@ void ChartView::manageSignals() {
|
||||
|
||||
void ChartView::updateLayout() {
|
||||
const ImVec2 grip = ImGui::CalcTextSize(icon::GRIP_HORIZONTAL);
|
||||
const ImVec2 top_left = layout_.rect.Min + ImVec2(LAYOUT_MARGINS.x, LAYOUT_MARGINS.y);
|
||||
const ImGuiStyle &style = ImGui::GetStyle();
|
||||
// WindowPadding can be zero in a borderless pane or drag preview. Chart
|
||||
// content always uses the shared control gap, independently of its parent.
|
||||
layout_.content_rect = layout_.rect;
|
||||
layout_.content_rect.Expand(-style.ItemSpacing.x);
|
||||
const ImVec2 top_left = layout_.content_rect.Min;
|
||||
layout_.move_icon_rect = ImRect(top_left, top_left + grip);
|
||||
const ImVec2 btn_size(iconButtonWidth(), iconButtonWidth());
|
||||
const ImVec2 close_min(layout_.rect.Max.x - ImGui::GetStyle().WindowPadding.x - btn_size.x, top_left.y);
|
||||
const ImVec2 close_min(layout_.content_rect.Max.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);
|
||||
const ImVec2 manage_min(close_min.x - btn_size.x - ImGui::GetStyle().ItemSpacing.x, top_left.y);
|
||||
layout_.manage_btn_rect = ImRect(manage_min, manage_min + btn_size);
|
||||
|
||||
ImFont *bold = boldFont();
|
||||
const float font_size = ImGui::GetFontSize();
|
||||
const float fm_height = ImGui::GetTextLineHeight();
|
||||
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 - ImGui::GetStyle().ItemInnerSpacing.x, legend_left + 10);
|
||||
const int row_height = std::max<int>(marker_size, fm_height) + fm_height + style.ItemInnerSpacing.y; // + the signal value line
|
||||
const int legend_left = layout_.move_icon_rect.Max.x + style.ItemSpacing.x;
|
||||
const int legend_right = std::max<int>(layout_.manage_btn_rect.Min.x - ImGui::GetStyle().ItemSpacing.x, legend_left + 10);
|
||||
|
||||
// layout legend entries left-to-right, wrapping between the move icon and the buttons
|
||||
layout_.legend_rects.clear();
|
||||
int x = legend_left, y = top_left.y;
|
||||
for (auto &s : sigs_) {
|
||||
int w = marker_size + LEGEND_SPACING + bold->CalcTextSizeA(font_size, FLT_MAX, 0.0f, s.sig->name.c_str()).x +
|
||||
int w = marker_size + style.ItemInnerSpacing.x + 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));
|
||||
@@ -166,13 +168,11 @@ void ChartView::updateLayout() {
|
||||
y += row_height;
|
||||
}
|
||||
layout_.legend_rects.emplace_back(ImVec2(x, y), ImVec2(x + w, y + std::max<int>(marker_size, fm_height)));
|
||||
x += w + 12;
|
||||
x += w + style.ItemSpacing.x;
|
||||
}
|
||||
|
||||
// add top space for the legend and signal values
|
||||
int adjust_top = (y + row_height) - top_left.y;
|
||||
adjust_top = std::max<int>(adjust_top, layout_.manage_btn_rect.Max.y - layout_.rect.Min.y + LAYOUT_MARGINS.y);
|
||||
layout_.header_bottom = layout_.rect.Min.y + adjust_top + LAYOUT_MARGINS.y;
|
||||
layout_.header_bottom = std::max<float>(y + row_height, layout_.manage_btn_rect.Max.y) + ImGui::GetStyle().ItemSpacing.y;
|
||||
}
|
||||
|
||||
void ChartView::updatePlot(double cur, double min, double max) {
|
||||
@@ -554,9 +554,9 @@ void ChartView::drawStaticLayer() {
|
||||
}
|
||||
|
||||
void ChartView::drawAxes() {
|
||||
ImGui::SetCursorScreenPos(ImVec2(layout_.rect.Min.x, layout_.header_bottom));
|
||||
const float plot_h = std::max(layout_.rect.Max.y - layout_.header_bottom - LAYOUT_MARGINS.w, 10.0f);
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(LAYOUT_MARGINS.x, AXIS_X_TOP_MARGIN));
|
||||
ImGui::SetCursorScreenPos(ImVec2(layout_.content_rect.Min.x, layout_.header_bottom));
|
||||
const float plot_h = std::max(layout_.content_rect.Max.y - layout_.header_bottom, 10.0f);
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0.0f, ImGui::GetStyle().ItemInnerSpacing.y));
|
||||
ImPlot::PushStyleColor(ImPlotCol_PlotBg, ImVec4(0, 0, 0, 0));
|
||||
ImPlot::PushStyleColor(ImPlotCol_FrameBg, ImVec4(0, 0, 0, 0));
|
||||
ImPlot::PushStyleColor(ImPlotCol_PlotBorder, palette().grid);
|
||||
@@ -568,8 +568,8 @@ void ChartView::drawAxes() {
|
||||
ImPlotFlags_NoBoxSelect | ImPlotFlags_NoInputs | ImPlotFlags_NoFrame;
|
||||
const ImPlotAxisFlags axis_flags = ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_NoSideSwitch | ImPlotAxisFlags_Lock;
|
||||
// reserve room for the right half of the last x tick label
|
||||
const float x_label_width = ImGui::CalcTextSize(formatNumber(x_max_, xAxisPrecision()).c_str()).x + 5;
|
||||
if (ImPlot::BeginPlot("##plot", ImVec2(layout_.rect.GetWidth() - x_label_width / 2, plot_h), flags)) {
|
||||
const float x_label_width = ImGui::CalcTextSize(formatNumber(x_max_, xAxisPrecision()).c_str()).x + ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
if (ImPlot::BeginPlot("##plot", ImVec2(layout_.content_rect.GetWidth() - x_label_width / 2, plot_h), flags)) {
|
||||
ImPlot::SetupAxis(ImAxis_X1, nullptr, axis_flags);
|
||||
ImPlot::SetupAxis(ImAxis_Y1, y_unit_.empty() ? nullptr : y_unit_.c_str(), axis_flags);
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, x_min_, x_max_, ImPlotCond_Always);
|
||||
@@ -624,7 +624,7 @@ void ChartView::drawLegend() {
|
||||
drawColorMarker(painter, r.Min, toImU32(s.color));
|
||||
}
|
||||
|
||||
float x = r.Min.x + marker_size + LEGEND_SPACING;
|
||||
float x = r.Min.x + marker_size + ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
const float text_y = r.GetCenter().y - font_size / 2.0f;
|
||||
addTextEllipsis(painter, bold, title_color, ImVec2(x, text_y), r.Max.x, s.sig->name);
|
||||
float name_w = std::min(bold->CalcTextSizeA(font_size, FLT_MAX, 0.0f, s.sig->name.c_str()).x, r.Max.x - x);
|
||||
@@ -633,7 +633,7 @@ void ChartView::drawLegend() {
|
||||
addTextEllipsis(painter, normal, msg_color, ImVec2(x, text_y), r.Max.x, msg);
|
||||
if (!s.visible) { // strike out
|
||||
const float y = r.GetCenter().y;
|
||||
painter->AddLine(ImVec2(r.Min.x + marker_size + LEGEND_SPACING, y), ImVec2(std::min(x + ImGui::CalcTextSize(msg.c_str()).x, r.Max.x), y), title_color);
|
||||
painter->AddLine(ImVec2(r.Min.x + marker_size + ImGui::GetStyle().ItemInnerSpacing.x, y), ImVec2(std::min(x + ImGui::CalcTextSize(msg.c_str()).x, r.Max.x), y), title_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,10 +730,10 @@ void ChartView::drawRubberBandTimeRange() {
|
||||
painter->PushClipRect(layout_.rect.Min, layout_.rect.Max);
|
||||
for (const auto &pt : {rubber_rect_.GetBL(), rubber_rect_.GetBR()}) {
|
||||
std::string sec = formatNumber(secondsAtPoint(pt), 2);
|
||||
ImVec2 size = ImGui::CalcTextSize(sec.c_str()) + ImVec2(12, AXIS_X_TOP_MARGIN * 2);
|
||||
ImVec2 size = ImGui::CalcTextSize(sec.c_str()) + ImVec2(12, ImGui::GetStyle().ItemInnerSpacing.y * 2);
|
||||
ImVec2 top_left = pt.x == rubber_rect_.Min.x ? ImVec2(pt.x - size.x, pt.y + 2) : ImVec2(pt.x, pt.y + 2);
|
||||
painter->AddRectFilled(top_left, top_left + size, badge, ImGui::GetStyle().FrameRounding);
|
||||
painter->AddText(top_left + ImVec2(6, AXIS_X_TOP_MARGIN), white, sec.c_str());
|
||||
painter->AddText(top_left + ImVec2(6, ImGui::GetStyle().ItemInnerSpacing.y), white, sec.c_str());
|
||||
}
|
||||
painter->PopClipRect();
|
||||
}
|
||||
@@ -745,7 +745,7 @@ void ChartView::drawTimeline() {
|
||||
|
||||
std::string time_str = formatNumber(cur_sec_, 2);
|
||||
ImVec2 time_str_size = ImGui::CalcTextSize(time_str.c_str()) + ImVec2(8, 2);
|
||||
ImVec2 time_str_pos(x - time_str_size.x / 2.0f, layout_.plot_area.Max.y + AXIS_X_TOP_MARGIN);
|
||||
ImVec2 time_str_pos(x - time_str_size.x / 2.0f, layout_.plot_area.Max.y + ImGui::GetStyle().ItemInnerSpacing.y);
|
||||
painter->AddRectFilled(time_str_pos, time_str_pos + time_str_size, ImGui::GetColorU32(palette().badge), ImGui::GetStyle().FrameRounding);
|
||||
painter->AddText(time_str_pos + ImVec2(4, 1), IM_COL32_WHITE, time_str.c_str());
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ private:
|
||||
// layout
|
||||
struct Layout {
|
||||
ImRect rect; // the whole chart widget, screen coordinates
|
||||
ImRect content_rect; // the same inset on all four sides, including during a drag
|
||||
ImRect plot_area;
|
||||
ImRect move_icon_rect;
|
||||
ImRect close_btn_rect;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "tools/cabana/utils/strings.h"
|
||||
|
||||
const int MAX_COLUMN_COUNT = 4;
|
||||
const int CHART_SPACING = 4;
|
||||
const int START_DRAG_DISTANCE = 10;
|
||||
const float MIN_RANGE_SLIDER_WIDTH = 40.0f;
|
||||
|
||||
@@ -166,12 +165,11 @@ void ChartsWidget::drawToolBar() {
|
||||
// the labels are captured by reference, they outlive the draw calls below
|
||||
std::vector<ToolbarItem> items;
|
||||
items.push_back({iconButtonWidth(), [this]() {
|
||||
if (iconButton("new_plot_btn", icon::PLUS_LG, "New Chart")) newChart();
|
||||
if (stepButton("new_plot_btn", true, "New Chart")) newChart();
|
||||
}});
|
||||
items.push_back({iconButtonWidth(), [this]() {
|
||||
if (iconButton("new_tab_btn", icon::WINDOW_PLUS, "New Tab")) newTab();
|
||||
}});
|
||||
items.back().tight = true;
|
||||
const std::string title_label = "Charts: " + std::to_string(charts_.size());
|
||||
items.push_back({ImGui::CalcTextSize(title_label.c_str()).x, [&title_label]() {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
@@ -231,11 +229,11 @@ void ChartsWidget::drawToolBar() {
|
||||
pushMonoFont(ImGui::GetFontSize());
|
||||
const float reset_zoom_width = iconTextButtonWidth(icon::ZOOM_OUT, widest + "-" + widest);
|
||||
popMonoFont();
|
||||
items.push_back({iconButtonWidth() * 2 + ImGui::GetStyle().ItemInnerSpacing.x, [this]() {
|
||||
items.push_back({iconButtonWidth() * 2 + ImGui::GetStyle().ItemSpacing.x, [this]() {
|
||||
ImGui::BeginDisabled(!zoom_undo_stack_.canUndo());
|
||||
if (iconButton("undo_zoom", icon::ARROW_COUNTERCLOCKWISE, "Undo Zoom")) zoom_undo_stack_.undo();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(!zoom_undo_stack_.canRedo());
|
||||
if (iconButton("redo_zoom", icon::ARROW_CLOCKWISE, "Redo Zoom")) zoom_undo_stack_.redo();
|
||||
ImGui::EndDisabled();
|
||||
@@ -247,12 +245,11 @@ void ChartsWidget::drawToolBar() {
|
||||
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;
|
||||
const char *dock_label = is_docked_ ? "Float the charts window" : "Dock the charts window";
|
||||
items.push_back(toolbarAction("dock_btn", dock_btn_icon, dock_label, [this]() { toggleChartsDocking(); }, true, true));
|
||||
items.push_back(toolbarAction("dock_btn", dock_btn_icon, dock_label, [this]() { toggleChartsDocking(); }));
|
||||
|
||||
// the slider shrinks first, the buttons stay pinned to the right edge
|
||||
if (slider_index != (size_t)-1) {
|
||||
@@ -356,7 +353,7 @@ void ChartsWidget::updateLayout() {
|
||||
|
||||
int n = MAX_COLUMN_COUNT;
|
||||
for (; n > 1; --n) {
|
||||
if ((n * CHART_MIN_WIDTH + (n - 1) * CHART_SPACING) < container_width) break;
|
||||
if ((n * CHART_MIN_WIDTH + (n - 1) * ImGui::GetStyle().ItemSpacing.x) < container_width) break;
|
||||
}
|
||||
|
||||
columns_action_visible_ = n > 1;
|
||||
@@ -625,14 +622,14 @@ void ChartsContainer::draw() {
|
||||
charts_widget_->updateLayout();
|
||||
|
||||
const int n = std::max(charts_widget_->current_column_count_, 1);
|
||||
const float spacing = CHART_SPACING;
|
||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
const float width = (geometry_.GetWidth() - (n - 1) * spacing) / n;
|
||||
const ImVec2 origin = ImGui::GetCursorScreenPos() + ImVec2(0, CHART_SPACING);
|
||||
const ImVec2 origin = ImGui::GetCursorScreenPos();
|
||||
auto current_charts = charts_widget_->currentCharts(); // copy: drawing may remove charts
|
||||
float bottom = origin.y;
|
||||
const bool aligned = ImPlot::BeginAlignedPlots("charts_align", true);
|
||||
for (int i = 0; i < current_charts.size(); ++i) {
|
||||
ImVec2 pos = origin + ImVec2((i % n) * (width + spacing), (i / n) * (settings.chart_height + spacing));
|
||||
ImVec2 pos = origin + ImVec2((i % n) * (width + spacing), (i / n) * (settings.chart_height + ImGui::GetStyle().ItemSpacing.y));
|
||||
ImGui::SetCursorScreenPos(pos);
|
||||
current_charts[i]->draw(width);
|
||||
bottom = std::max(bottom, pos.y + settings.chart_height);
|
||||
@@ -640,15 +637,15 @@ void ChartsContainer::draw() {
|
||||
}
|
||||
if (aligned) ImPlot::EndAlignedPlots();
|
||||
ImGui::SetCursorScreenPos(ImVec2(origin.x, bottom));
|
||||
ImGui::Dummy(ImVec2(geometry_.GetWidth(), CHART_SPACING));
|
||||
geometry_.Max.y = bottom + CHART_SPACING;
|
||||
ImGui::Dummy(ImVec2(geometry_.GetWidth(), ImGui::GetStyle().ItemSpacing.y));
|
||||
geometry_.Max.y = bottom + ImGui::GetStyle().ItemSpacing.y;
|
||||
drawDropIndicator();
|
||||
}
|
||||
|
||||
void ChartsContainer::drawDropIndicator() {
|
||||
if (!(drop_indicator_pos_.x == 0 && drop_indicator_pos_.y == 0) && !childAt(drop_indicator_pos_)) {
|
||||
ImRect r = geometry_;
|
||||
r.Max.y = r.Min.y + CHART_SPACING;
|
||||
r.Max.y = r.Min.y + ImGui::GetStyle().ItemSpacing.y;
|
||||
if (auto insert_after = getDropAfter(drop_indicator_pos_)) {
|
||||
float h = r.GetHeight();
|
||||
r.Min.y = insert_after->rect().Max.y;
|
||||
|
||||
@@ -41,6 +41,7 @@ bool SignalSelector::draw() {
|
||||
const float lists_h = ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() * 3;
|
||||
|
||||
ImGui::BeginGroup();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Available Signals");
|
||||
// a combo popup with a filter box
|
||||
const char *preview = msgs_combo_index_ >= 0 ? msgs_combo_[msgs_combo_index_].text.c_str() : "Select a message...";
|
||||
@@ -79,6 +80,7 @@ bool SignalSelector::draw() {
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginGroup();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Selected Signals");
|
||||
bool remove_dbl = false;
|
||||
drawList("##selected_list", selected_list_, &selected_row_, true, &remove_dbl, ImVec2(column_w, lists_h + ImGui::GetFrameHeightWithSpacing()));
|
||||
|
||||
@@ -188,10 +188,12 @@ void draw() {
|
||||
if (!pending_dir.empty()) setDir(pending_dir);
|
||||
|
||||
if (s.mode != Mode::Directory) {
|
||||
ImGui::SetNextItemWidth(-90.0f);
|
||||
const std::string filter = s.extension.empty() ? "*" : "*" + s.extension;
|
||||
ImGui::SetNextItemWidth(-(ImGui::CalcTextSize(filter.c_str()).x + ImGui::GetStyle().ItemSpacing.x));
|
||||
if (inputText("##name", &s.filename, "File name", ImGuiInputTextFlags_EnterReturnsTrue)) ok = true;
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("%s", s.extension.empty() ? "*" : ("*" + s.extension).c_str());
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextDisabled("%s", filter.c_str());
|
||||
}
|
||||
const char *accept_label = s.mode == Mode::SaveFile ? "Save" : (s.mode == Mode::Directory ? "Choose" : "Open");
|
||||
dialogButtons(accept_label, &ok, &cancel);
|
||||
|
||||
@@ -23,7 +23,7 @@ const char *FORM_LABELS[FORM_LABEL_COUNT] = {"Color Theme", "Max Cached Minutes"
|
||||
float formLabelWidth() {
|
||||
float w = 0.0f;
|
||||
for (const char *label : FORM_LABELS) w = std::max(w, ImGui::CalcTextSize(label).x);
|
||||
return w + ImGui::GetStyle().ItemSpacing.x * 2; // horizontal spacing between label and field
|
||||
return ImGui::GetCursorPosX() + w + ImGui::GetStyle().ItemSpacing.x;
|
||||
}
|
||||
|
||||
void formRow(FormLabel label, float label_width) {
|
||||
@@ -34,22 +34,8 @@ void formRow(FormLabel label, float label_width) {
|
||||
}
|
||||
|
||||
void settingInputInt(const char *id, int *value, int step, int step_fast, int minimum, int maximum) {
|
||||
const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
const float width = ImGui::CalcItemWidth();
|
||||
ImGui::PushID(id);
|
||||
ImGui::BeginGroup();
|
||||
ImGui::SetNextItemWidth(width - 2 * (iconButtonWidth() + spacing));
|
||||
ImGui::InputInt("##value", value, 0);
|
||||
inputInt((std::string("##") + id).c_str(), value, step, step_fast);
|
||||
*value = std::clamp(*value, minimum, maximum);
|
||||
const int increment = ImGui::GetIO().KeyCtrl ? step_fast : step;
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
if (iconButton("decrement", icon::DASH_LG)) *value = std::max(minimum, *value - increment);
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
if (iconButton("increment", icon::PLUS_LG)) *value = std::min(maximum, *value + increment);
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::EndGroup();
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -89,7 +75,7 @@ void SettingsDialog::draw() {
|
||||
|
||||
checkBox("Enable live stream logging", &log_livestream_);
|
||||
ImGui::BeginDisabled(!log_livestream_);
|
||||
ImGui::SetNextItemWidth(-90.0f);
|
||||
ImGui::SetNextItemWidth(-(toolbarButtonWidth("Browse...") + ImGui::GetStyle().ItemSpacing.x));
|
||||
inputText("##log_path", &log_path_, "", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Browse...")) {
|
||||
|
||||
@@ -18,7 +18,8 @@ void OpenReplayWidget::draw() {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Route");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(-250.0f);
|
||||
ImGui::SetNextItemWidth(-(toolbarButtonWidth("Remote Route...") + toolbarButtonWidth("Local Route...") +
|
||||
ImGui::GetStyle().ItemSpacing.x * 2));
|
||||
inputText("##route", &route_, "Enter a route name or browse for a local or remote route");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Remote Route...")) {
|
||||
@@ -134,7 +135,7 @@ void OpenPandaWidget::draw() {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Serial");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(-100.0f);
|
||||
ImGui::SetNextItemWidth(-(toolbarButtonWidth("Refresh") + ImGui::GetStyle().ItemSpacing.x));
|
||||
if (comboBox("##serial", &serial_index_, serials_)) buildConfigForm();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Refresh")) {
|
||||
@@ -228,10 +229,10 @@ void OpenSocketCanWidget::draw() {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Device");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(300.0f);
|
||||
ImGui::SetNextItemWidth(-(toolbarButtonWidth("Refresh") + ImGui::GetStyle().ItemSpacing.x));
|
||||
if (comboBox("##device", &device_index_, devices_)) config.device = devices_[device_index_];
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Refresh", ImVec2(100.0f, 0.0f))) refreshDevices();
|
||||
if (ImGui::Button("Refresh")) refreshDevices();
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractStream> OpenSocketCanWidget::open() {
|
||||
@@ -289,7 +290,7 @@ void StreamSelector::draw() {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("DBC File");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(-90.0f);
|
||||
ImGui::SetNextItemWidth(-(toolbarButtonWidth("Browse...") + ImGui::GetStyle().ItemSpacing.x));
|
||||
inputText("##dbc", &dbc_file_, "Choose a DBC file to open", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Browse...")) {
|
||||
|
||||
@@ -904,16 +904,18 @@ void MainWindow::drawVideoPanel() {
|
||||
}
|
||||
}
|
||||
// Replay uses a splitter for the gap; live streams use normal item spacing.
|
||||
if (!charts_floating_ && !live) ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
|
||||
if (video_h > 0.0f) {
|
||||
ImGui::BeginChild("video", ImVec2(0, video_h), ImGuiChildFlags_Borders);
|
||||
help_overlay_.add(video_widget_->whatsThis(), ImGui::GetCurrentWindow()->Rect());
|
||||
video_widget_->draw();
|
||||
ImGui::EndChild();
|
||||
// The splitter supplies the pane gap; keep normal spacing inside the video child.
|
||||
if (!charts_floating_ && !live) ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().ItemSpacing.y);
|
||||
} else {
|
||||
video_widget_->setVisible(false); // the splitter collapsed the video: stop the vipc thread
|
||||
}
|
||||
if (!charts_floating_ && !live) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
|
||||
ImGui::InvisibleButton("##splitter", ImVec2(-1.0f, splitter_h));
|
||||
const bool splitter_hovered = ImGui::IsItemHovered() && !live, splitter_active = ImGui::IsItemActive() && !live;
|
||||
if (splitter_active) {
|
||||
|
||||
@@ -103,9 +103,10 @@ void applyTheme(int theme) {
|
||||
style.WindowBorderSize = 1.0f;
|
||||
style.FrameBorderSize = 1.0f;
|
||||
style.TabBorderSize = 1.0f;
|
||||
style.WindowPadding = ImVec2(12.0f, 10.0f);
|
||||
style.FramePadding = ImVec2(9.0f, 5.0f);
|
||||
style.ItemSpacing = ImVec2(10.0f, 8.0f);
|
||||
style.WindowPadding = ImVec2(spacing::CONTROL, spacing::CONTROL);
|
||||
style.FramePadding = ImVec2(spacing::CONTROL, spacing::INNER);
|
||||
style.ItemSpacing = ImVec2(spacing::CONTROL, spacing::CONTROL);
|
||||
style.ItemInnerSpacing = ImVec2(spacing::INNER, spacing::INNER);
|
||||
style.CellPadding = ImVec2(6.0f, 4.0f);
|
||||
style.ScrollbarSize = 14.0f;
|
||||
style.GrabMinSize = 13.0f;
|
||||
|
||||
@@ -32,6 +32,14 @@ inline ImU32 toImU32(const CabanaColor &c) { return IM_COL32(c.r, c.g, c.b, c.a)
|
||||
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); }
|
||||
|
||||
// Logical pixels. External control gaps are equal on both axes; inner spacing
|
||||
// is reserved for parts of one control (icon/label, checkbox/label, dropdown arrow).
|
||||
namespace spacing {
|
||||
constexpr float CONTROL = 8.0f;
|
||||
constexpr float INNER = 4.0f;
|
||||
constexpr float DIALOG_BUTTON_MIN_WIDTH = 80.0f;
|
||||
} // namespace spacing
|
||||
|
||||
constexpr float UI_FONT_SIZE = 16.0f;
|
||||
|
||||
void loadFonts();
|
||||
|
||||
@@ -76,12 +76,15 @@ bool FindSignalDlg::draw() {
|
||||
}
|
||||
searching_ = search_future_.valid();
|
||||
if (begin(ImVec2(900, 650))) {
|
||||
float group_w = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) / 2;
|
||||
ImGui::BeginChild("Messages", ImVec2(group_w, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY);
|
||||
const ImGuiStyle &style = ImGui::GetStyle();
|
||||
const float group_w = (ImGui::GetContentRegionAvail().x - style.ItemSpacing.x) / 2;
|
||||
const float group_h = ImGui::GetTextLineHeightWithSpacing() + ImGui::GetFrameHeightWithSpacing() * 4 +
|
||||
style.WindowPadding.y * 2 - style.ItemSpacing.y;
|
||||
ImGui::BeginChild("Messages", ImVec2(group_w, group_h), ImGuiChildFlags_Borders);
|
||||
drawMessageGroup();
|
||||
ImGui::EndChild();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginChild("Signal", ImVec2(group_w, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY);
|
||||
ImGui::BeginChild("Signal", ImVec2(group_w, group_h), ImGuiChildFlags_Borders);
|
||||
drawPropertiesGroup();
|
||||
ImGui::EndChild();
|
||||
float footer = searched_ ? ImGui::GetTextLineHeightWithSpacing() : 0;
|
||||
@@ -98,20 +101,21 @@ bool FindSignalDlg::draw() {
|
||||
|
||||
void FindSignalDlg::drawMessageGroup() {
|
||||
ImGui::BeginDisabled(searching_ || !search_.histories.empty());
|
||||
const float field_x = ImGui::GetCursorPosX() + ImGui::CalcTextSize("Address").x + ImGui::GetStyle().ItemSpacing.x;
|
||||
ImGui::TextUnformatted("Messages");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Bus");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SameLine(field_x);
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
inputText("##bus", &bus_, "Comma-separated values. Leave blank for all.");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Address");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SameLine(field_x);
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
inputText("##address", &address_, "Comma-separated hex values. Leave blank for all.");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Time");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SameLine(field_x);
|
||||
ImGui::SetNextItemWidth(70);
|
||||
validatedText("##first_time", &first_time_, validateDouble);
|
||||
ImGui::SameLine();
|
||||
@@ -126,29 +130,30 @@ void FindSignalDlg::drawMessageGroup() {
|
||||
|
||||
void FindSignalDlg::drawPropertiesGroup() {
|
||||
ImGui::BeginDisabled(searching_ || !search_.histories.empty());
|
||||
const float field_x = ImGui::GetCursorPosX() + ImGui::CalcTextSize("Factor").x + ImGui::GetStyle().ItemSpacing.x;
|
||||
ImGui::TextUnformatted("Signal");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Size");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(70);
|
||||
if (ImGui::InputInt("##min_size", &min_size_, 1, 10)) min_size_ = std::clamp(min_size_, 1, 64);
|
||||
ImGui::SameLine(field_x);
|
||||
ImGui::SetNextItemWidth(inputIntWidth(2));
|
||||
if (inputInt("##min_size", &min_size_, 1, 10)) min_size_ = std::clamp(min_size_, 1, 64);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("-");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(70);
|
||||
if (ImGui::InputInt("##max_size", &max_size_, 1, 10)) max_size_ = std::clamp(max_size_, 1, 64);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(inputIntWidth(2));
|
||||
if (inputInt("##max_size", &max_size_, 1, 10)) max_size_ = std::clamp(max_size_, 1, 64);
|
||||
ImGui::SetCursorPosX(field_x);
|
||||
checkBox("Little Endian", &little_endian_);
|
||||
ImGui::SameLine();
|
||||
checkBox("Signed", &is_signed_);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Factor");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SameLine(field_x);
|
||||
ImGui::SetNextItemWidth(100);
|
||||
validatedText("##factor", &factor_, validateDouble);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Offset");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SameLine(field_x);
|
||||
ImGui::SetNextItemWidth(100);
|
||||
validatedText("##offset", &offset_, validateDouble);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
@@ -30,9 +30,10 @@ void FindSimilarBitsDlg::updateMessages() {
|
||||
|
||||
bool FindSimilarBitsDlg::draw() {
|
||||
if (begin(ImVec2(700, 500))) {
|
||||
const float field_x = ImGui::GetCursorPosX() + ImGui::CalcTextSize("Find From:").x + ImGui::GetStyle().ItemSpacing.x;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Find From:");
|
||||
ImGui::SameLine(90);
|
||||
ImGui::SameLine(field_x);
|
||||
ImGui::TextUnformatted("Bus");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(60);
|
||||
@@ -40,20 +41,21 @@ bool FindSimilarBitsDlg::draw() {
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(200);
|
||||
comboBox("##msg", &msg_index_, msg_names_);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(field_x);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Byte Index");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80);
|
||||
if (ImGui::InputInt("##byte_idx", &byte_idx_, 1, 10)) byte_idx_ = std::clamp(byte_idx_, 0, 63);
|
||||
ImGui::SetNextItemWidth(inputIntWidth(2));
|
||||
if (inputInt("##byte_idx", &byte_idx_, 1, 10)) byte_idx_ = std::clamp(byte_idx_, 0, 63);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("Bit Index");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80);
|
||||
if (ImGui::InputInt("##bit_idx", &bit_idx_, 1, 10)) bit_idx_ = std::clamp(bit_idx_, 0, 7);
|
||||
ImGui::SetNextItemWidth(inputIntWidth(1));
|
||||
if (inputInt("##bit_idx", &bit_idx_, 1, 10)) bit_idx_ = std::clamp(bit_idx_, 0, 7);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Find In:");
|
||||
ImGui::SameLine(90);
|
||||
ImGui::SameLine(field_x);
|
||||
ImGui::TextUnformatted("Bus");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(60);
|
||||
@@ -66,8 +68,8 @@ bool FindSimilarBitsDlg::draw() {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("Minimum Message Count");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80);
|
||||
if (ImGui::InputInt("##min_msgs", &min_msgs_, 1, 10)) min_msgs_ = std::max(min_msgs_, 0);
|
||||
ImGui::SetNextItemWidth(inputIntWidth(4));
|
||||
if (inputInt("##min_msgs", &min_msgs_, 1, 10)) min_msgs_ = std::max(min_msgs_, 0);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Find")) find();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -71,13 +72,13 @@ bool beginControlChild(const char *id, const ImVec2 &size, ImGuiWindowFlags flag
|
||||
|
||||
bool clearableInput(const char *label, std::string *s, const char *hint, ImGuiInputTextCallback validator) {
|
||||
const float width = ImGui::CalcItemWidth();
|
||||
const float clear_width = iconButtonWidth() + ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
const float clear_width = iconButtonWidth() + ImGui::GetStyle().ItemSpacing.x;
|
||||
const bool show_clear = !s->empty() && width >= clear_width + ImGui::GetFrameHeight();
|
||||
ImGui::SetNextItemWidth(show_clear ? width - clear_width : width);
|
||||
ImGui::BeginGroup();
|
||||
bool changed = validatedInput(label, s, validator, hint);
|
||||
if (show_clear) {
|
||||
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::SameLine();
|
||||
ImGui::PushID(label);
|
||||
if (iconButton("clear", icon::X_LG)) {
|
||||
s->clear();
|
||||
@@ -205,11 +206,10 @@ bool squareIconButton(const char *id, const char *icon) {
|
||||
ImFontBaked *baked = ImGui::GetFont()->GetFontBaked(size);
|
||||
if (const ImFontGlyph *g = baked->FindGlyph((ImWchar)codepoint)) {
|
||||
const GlyphInk ink = cachedGlyphInk(g, size, codepoint);
|
||||
// Preserve half-logical-pixel positions on HiDPI displays.
|
||||
const float snap = std::max(1.0f, ImGui::GetIO().DisplayFramebufferScale.x);
|
||||
auto snapped = [snap](float v) { return std::round(v * snap) / snap; };
|
||||
const ImVec2 pos(snapped(r.GetCenter().x - (ink.x0 + ink.x1) * 0.5f), snapped(r.GetCenter().y - (ink.y0 + ink.y1) * 0.5f));
|
||||
// AddText truncates to whole logical pixels, undoing the framebuffer snapping above.
|
||||
// Keep the exact ink center, including half pixels when glyph and button sizes
|
||||
// have different parity. Rounding the origin shifts small icons off center.
|
||||
const ImVec2 pos(r.GetCenter().x - (ink.x0 + ink.x1) * 0.5f, r.GetCenter().y - (ink.y0 + ink.y1) * 0.5f);
|
||||
// AddText truncates the origin, so draw the atlas glyph directly.
|
||||
ImGui::GetWindowDrawList()->AddImage(ImGui::GetIO().Fonts->TexRef, ImVec2(pos.x + g->X0, pos.y + g->Y0),
|
||||
ImVec2(pos.x + g->X1, pos.y + g->Y1), ImVec2(g->U0, g->V0), ImVec2(g->U1, g->V1),
|
||||
ImGui::GetColorU32(ImGuiCol_Text));
|
||||
@@ -290,7 +290,8 @@ bool dialogEscapePressed() {
|
||||
|
||||
bool dialogButtons(const char *accept_label, bool *accepted, bool *rejected, bool accept_enabled,
|
||||
const char *reject_label) {
|
||||
const float button_width = 80.0f;
|
||||
const float button_width = std::max({spacing::DIALOG_BUTTON_MIN_WIDTH, toolbarButtonWidth(accept_label),
|
||||
reject_label ? toolbarButtonWidth(reject_label) : 0.0f});
|
||||
const int count = reject_label ? 2 : 1;
|
||||
const float total = button_width * count + ImGui::GetStyle().ItemSpacing.x * (count - 1);
|
||||
const float avail = ImGui::GetContentRegionAvail().x;
|
||||
@@ -316,6 +317,52 @@ bool dialogButtons(const char *accept_label, bool *accepted, bool *rejected, boo
|
||||
return pressed;
|
||||
}
|
||||
|
||||
float inputIntWidth(int digits) {
|
||||
const ImGuiStyle &style = ImGui::GetStyle();
|
||||
return ImGui::CalcTextSize(std::string(digits, '0').c_str()).x + style.FramePadding.x * 2 +
|
||||
(ImGui::GetFrameHeight() + style.ItemSpacing.x) * 2;
|
||||
}
|
||||
|
||||
bool stepButton(const char *id, bool increment, const char *tooltip) {
|
||||
return iconButton(id, increment ? icon::PLUS_LG : icon::DASH_LG, tooltip);
|
||||
}
|
||||
|
||||
bool inputInt(const char *label, int *value, int step, int step_fast, ImGuiInputTextFlags flags) {
|
||||
if (step <= 0) return ImGui::InputInt(label, value, 0, 0, flags);
|
||||
|
||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
const float width = ImGui::CalcItemWidth();
|
||||
ImGui::BeginGroup();
|
||||
ImGui::PushID(label);
|
||||
ImGui::SetNextItemWidth(std::max(1.0f, width - 2 * (iconButtonWidth() + spacing)));
|
||||
bool changed = ImGui::InputInt("##value", value, 0, 0, flags);
|
||||
ImGui::BeginDisabled(flags & ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
|
||||
for (bool increment : {false, true}) {
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
if (stepButton(increment ? "increment" : "decrement", increment)) {
|
||||
const int amount = ImGui::GetIO().KeyCtrl && step_fast > 0 ? step_fast : step;
|
||||
const int next = std::clamp<int64_t>(int64_t(*value) + (increment ? int64_t(amount) : -int64_t(amount)),
|
||||
std::numeric_limits<int>::min(), std::numeric_limits<int>::max());
|
||||
if (next != *value) {
|
||||
*value = next;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::EndDisabled();
|
||||
const char *label_end = ImGui::FindRenderedTextEnd(label);
|
||||
if (label != label_end) {
|
||||
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::TextUnformatted(label, label_end);
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::EndGroup();
|
||||
if (changed) ImGui::MarkItemEdited(ImGui::GetItemID());
|
||||
return changed;
|
||||
}
|
||||
|
||||
int tableHeadersRow() {
|
||||
int clicked = -1;
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
|
||||
@@ -449,16 +496,16 @@ bool beginDialog(const char *id, PopupOwner *owner, const ImVec2 &size, ImGuiWin
|
||||
|
||||
// tool bar
|
||||
|
||||
ToolbarItem toolbarAction(const char *id, const char *icon, const char *label, std::function<void()> trigger, bool enabled, bool tight) {
|
||||
ToolbarItem toolbarAction(const char *id, const char *icon, const char *label, std::function<void()> trigger, bool enabled) {
|
||||
return {iconButtonWidth(), [=]() {
|
||||
ImGui::BeginDisabled(!enabled);
|
||||
if (iconButton(id, icon)) trigger();
|
||||
ImGui::EndDisabled();
|
||||
disabledItemTooltip(label);
|
||||
}, label, trigger, enabled, true, tight};
|
||||
}, label, trigger, enabled, true};
|
||||
}
|
||||
|
||||
ToolbarItem toolbarMenu(const char *id, const std::string &text, const char *label, std::function<void()> items, bool bold, bool tight, float width) {
|
||||
ToolbarItem toolbarMenu(const char *id, const std::string &text, const char *label, std::function<void()> items, bool bold, float width) {
|
||||
if (width <= 0.0f) width = menuButtonWidth(text, bold);
|
||||
ToolbarItem item{width, [id, text, items, bold, width]() {
|
||||
const std::string popup_id = std::string(id) + "_menu";
|
||||
@@ -468,7 +515,6 @@ ToolbarItem toolbarMenu(const char *id, const std::string &text, const char *lab
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}, label};
|
||||
item.tight = tight;
|
||||
item.submenu = std::move(items);
|
||||
return item;
|
||||
}
|
||||
@@ -477,13 +523,9 @@ float toolbarButtonWidth(const std::string &label) {
|
||||
return ImGui::CalcTextSize(label.c_str(), nullptr, true).x + ImGui::GetStyle().FramePadding.x * 2;
|
||||
}
|
||||
|
||||
static float toolbarSpacing(const ToolbarItem &item) {
|
||||
return item.tight ? ImGui::GetStyle().ItemInnerSpacing.x : ImGui::GetStyle().ItemSpacing.x;
|
||||
}
|
||||
|
||||
static float toolbarGroupWidth(const std::vector<ToolbarItem> &items, size_t begin, size_t end) {
|
||||
float w = 0;
|
||||
for (size_t i = begin; i < end; ++i) w += items[i].width + (i > begin ? toolbarSpacing(items[i]) : 0);
|
||||
for (size_t i = begin; i < end; ++i) w += items[i].width + (i > begin ? ImGui::GetStyle().ItemSpacing.x : 0);
|
||||
return w;
|
||||
}
|
||||
|
||||
@@ -512,7 +554,7 @@ void drawToolbar(const std::vector<ToolbarItem> &items, size_t spacer_index, flo
|
||||
const float usable = avail - (extension_width + style.ItemSpacing.x);
|
||||
float used = 0;
|
||||
for (visible = 0; visible < items.size(); ++visible) {
|
||||
const float w = items[visible].width + (visible ? toolbarSpacing(items[visible]) : 0);
|
||||
const float w = items[visible].width + (visible ? style.ItemSpacing.x : 0);
|
||||
if (used + w > usable) break;
|
||||
used += w;
|
||||
}
|
||||
@@ -521,7 +563,7 @@ void drawToolbar(const std::vector<ToolbarItem> &items, size_t spacer_index, flo
|
||||
for (size_t i = 0; i < visible; ++i) {
|
||||
if (i == 0) ImGui::SetCursorPosX(start_x);
|
||||
else if (fits && i == spacer_index) ImGui::SameLine(right_edge - right_width);
|
||||
else ImGui::SameLine(0.0f, toolbarSpacing(items[i]));
|
||||
else ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.x);
|
||||
items[i].draw();
|
||||
}
|
||||
|
||||
@@ -557,11 +599,10 @@ void drawToolbar(const std::vector<ToolbarItem> &items, size_t spacer_index, flo
|
||||
}
|
||||
|
||||
const float MENU_ARROW_SIZE = 6.0f; // dropdown arrow on a menu button
|
||||
const float MENU_ARROW_SPACING = 5.0f; // gap between the label and the dropdown arrow
|
||||
|
||||
float menuButtonWidth(const std::string &text, bool bold) {
|
||||
if (bold) pushBoldFont();
|
||||
const float w = ImGui::CalcTextSize(text.c_str(), nullptr, true).x + MENU_ARROW_SPACING + MENU_ARROW_SIZE +
|
||||
const float w = ImGui::CalcTextSize(text.c_str(), nullptr, true).x + ImGui::GetStyle().ItemInnerSpacing.x + MENU_ARROW_SIZE +
|
||||
ImGui::GetStyle().FramePadding.x * 2;
|
||||
if (bold) popBoldFont();
|
||||
return w;
|
||||
@@ -576,7 +617,7 @@ bool menuButton(const char *id, const std::string &text, const char *popup_id, b
|
||||
const float text_width = ImGui::CalcTextSize(text.c_str(), nullptr, true).x;
|
||||
const float ascent = ImGui::GetFontBaked()->Ascent;
|
||||
// the text and the arrow are centered as a group in the button
|
||||
const float padding_x = std::max(style.FramePadding.x, (width - (text_width + MENU_ARROW_SPACING + MENU_ARROW_SIZE)) * 0.5f);
|
||||
const float padding_x = std::max(style.FramePadding.x, (width - (text_width + ImGui::GetStyle().ItemInnerSpacing.x + MENU_ARROW_SIZE)) * 0.5f);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, popup_open ? style.Colors[ImGuiCol_ButtonActive] : style.Colors[ImGuiCol_Button]);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(padding_x, style.FramePadding.y));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f));
|
||||
@@ -588,7 +629,7 @@ bool menuButton(const char *id, const std::string &text, const char *popup_id, b
|
||||
if (bold) popBoldFont();
|
||||
// a 6 px arrow right after the text, sitting on the text baseline
|
||||
const ImVec2 min = ImGui::GetItemRectMin();
|
||||
const float x = min.x + padding_x + text_width + MENU_ARROW_SPACING;
|
||||
const float x = min.x + padding_x + text_width + ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
const float baseline = min.y + style.FramePadding.y + ascent;
|
||||
ImGui::GetWindowDrawList()->AddTriangleFilled(ImVec2(x, baseline - MENU_ARROW_SIZE * 0.5f),
|
||||
ImVec2(x + MENU_ARROW_SIZE, baseline - MENU_ARROW_SIZE * 0.5f),
|
||||
|
||||
@@ -83,6 +83,7 @@ inline std::string shortcut(const char *keys) { return std::string(MOD_KEY) + "+
|
||||
// Use ItemInnerSpacing between related buttons and ItemSpacing between groups.
|
||||
bool iconButton(const char *id, const char *icon, const char *tooltip = nullptr);
|
||||
float iconButtonWidth();
|
||||
bool stepButton(const char *id, bool increment, const char *tooltip = nullptr);
|
||||
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);
|
||||
|
||||
@@ -115,6 +116,12 @@ ImGuiWindow *topPopupWindow();
|
||||
bool dialogButtons(const char *accept_label, bool *accepted, bool *rejected, bool accept_enabled = true,
|
||||
const char *reject_label = "Cancel");
|
||||
|
||||
// Numeric inputs keep the same external gaps as other button rows. Width includes
|
||||
// a readable value plus both step buttons; use for compact fixed-width fields.
|
||||
float inputIntWidth(int digits);
|
||||
bool inputInt(const char *label, int *value, int step = 1, int step_fast = 100,
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags_None);
|
||||
|
||||
// horizontal header labels are centered. Returns the column a right click was released on, or -1.
|
||||
int tableHeadersRow();
|
||||
|
||||
@@ -168,15 +175,14 @@ struct ToolbarItem {
|
||||
std::function<void()> trigger;
|
||||
bool enabled = true;
|
||||
bool in_menu = true; // false: left out of the ">>" menu (a separator)
|
||||
bool tight = false; // true: ItemInnerSpacing before it, it belongs to the previous item's group
|
||||
std::function<void()> submenu; // set: the ">>" entry is a submenu with these items instead of an action
|
||||
};
|
||||
ToolbarItem toolbarAction(const char *id, const char *icon, const char *label, std::function<void()> trigger,
|
||||
bool enabled = true, bool tight = false);
|
||||
bool enabled = true);
|
||||
// A drop-down button that opens `items` in a popup; in the overflow menu they become a submenu.
|
||||
// width 0: sized to the text.
|
||||
ToolbarItem toolbarMenu(const char *id, const std::string &text, const char *label, std::function<void()> items,
|
||||
bool bold = false, bool tight = false, float width = 0.0f);
|
||||
bool bold = false, float width = 0.0f);
|
||||
float toolbarButtonWidth(const std::string &label);
|
||||
// the width of every item plus the spacing between neighbors and the two groups
|
||||
float toolbarWidth(const std::vector<ToolbarItem> &items, size_t spacer_index);
|
||||
|
||||
@@ -102,7 +102,7 @@ void DetailWidget::drawToolBar() {
|
||||
const float panel_width = ImGui::GetWindowWidth();
|
||||
items.push_back(toolbarAction("edit_msg", icon::PENCIL, "Edit Message", [this, panel_width]() { editMsg(panel_width); }));
|
||||
items.push_back(toolbarAction("remove_msg", icon::TRASH, "Remove Message",
|
||||
[this]() { UndoStack::instance()->push(new RemoveMsgCommand(msg_id_)); }, action_remove_msg_enabled_, true));
|
||||
[this]() { UndoStack::instance()->push(new RemoveMsgCommand(msg_id_)); }, action_remove_msg_enabled_));
|
||||
|
||||
const float right_width = toolbarWidth(items, spacer_index) - style.ItemSpacing.x;
|
||||
name_width = std::max(ImGui::CalcTextSize("MMMMMM").x, ImGui::GetContentRegionAvail().x - right_width - style.ItemSpacing.x);
|
||||
@@ -221,13 +221,12 @@ void DetailWidget::drawTabWidget() {
|
||||
// binary_view_ keeps its size hint, signal_view_ takes the rest
|
||||
const float min_height = binary_view_->minimumSizeHint().y;
|
||||
const float avail = ImGui::GetContentRegionAvail().y;
|
||||
const float max_height = std::max(avail - 6.0f - ImGui::GetStyle().ItemSpacing.y * 2 - 1.0f, 1.0f);
|
||||
const float max_height = std::max(avail - style.ItemSpacing.y - 1.0f, 1.0f);
|
||||
const float height = std::clamp(min_height, 1.0f, max_height);
|
||||
ImGui::BeginChild("binary_view", ImVec2(0, height), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
binary_view_rect_ = ImGui::GetCurrentWindow()->Rect();
|
||||
binary_view_->draw();
|
||||
ImGui::EndChild();
|
||||
ImGui::Dummy(ImVec2(0.0f, 6.0f));
|
||||
ImGui::BeginChild("signal_view", ImVec2(0, 0));
|
||||
signal_view_rect_ = ImGui::GetCurrentWindow()->Rect();
|
||||
signal_view_->draw();
|
||||
@@ -338,7 +337,7 @@ bool EditMessageDialog::draw() {
|
||||
setNextDialogWindow(ImVec2(std::clamp(width_, min_width, max_width), 0.0f));
|
||||
bool open = true;
|
||||
if (ImGui::BeginPopupModal(window_title_.c_str(), &open)) {
|
||||
const float label_width = ImGui::CalcTextSize("Comment").x + ImGui::GetStyle().ItemSpacing.x * 2;
|
||||
const float label_width = ImGui::GetCursorPosX() + ImGui::CalcTextSize("Comment").x + ImGui::GetStyle().ItemSpacing.x;
|
||||
auto row = [&](const char *label) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(label);
|
||||
@@ -356,7 +355,7 @@ bool EditMessageDialog::draw() {
|
||||
}
|
||||
|
||||
row("Size");
|
||||
if (ImGui::InputInt("##size", &size_spin_)) size_spin_ = std::clamp(size_spin_, 1, CAN_MAX_DATA_BYTES);
|
||||
if (inputInt("##size", &size_spin_)) size_spin_ = std::clamp(size_spin_, 1, CAN_MAX_DATA_BYTES);
|
||||
|
||||
row("Node");
|
||||
validatedInput("##node", &node_, nameValidator);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
namespace {
|
||||
float scrollButtonsWidth() {
|
||||
const ImGuiStyle &style = ImGui::GetStyle();
|
||||
return style.ItemSpacing.x + ImGui::GetFrameHeight() * 2.0f + style.ItemInnerSpacing.x;
|
||||
return (ImGui::GetFrameHeight() + style.ItemSpacing.x) * 2.0f;
|
||||
}
|
||||
|
||||
void drawScrollButtons(ImGuiTabBar *tab_bar) {
|
||||
@@ -22,7 +22,7 @@ void drawScrollButtons(ImGuiTabBar *tab_bar) {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
const bool left = i == 0;
|
||||
ImGui::SetCursorScreenPos(ImVec2(start_x + i * (size + style.ItemInnerSpacing.x), tab_bar->BarRect.Min.y));
|
||||
ImGui::SetCursorScreenPos(ImVec2(start_x + i * (size + style.ItemSpacing.x), tab_bar->BarRect.Min.y));
|
||||
ImGui::BeginDisabled(left ? tab_bar->ScrollingTarget <= 0.0f : tab_bar->ScrollingTarget >= max_scroll);
|
||||
if (ImGui::Button(left ? "###scroll_left" : "###scroll_right", ImVec2(size, size))) {
|
||||
const float step = (left ? -4.0f : 4.0f) * ImGui::GetFontSize();
|
||||
|
||||
@@ -334,15 +334,18 @@ void SignalView::drawEditor(SignalModel::Item *item) {
|
||||
|
||||
drawLineEditor(item, validator, take_focus);
|
||||
} else if (item->type == SignalModel::Item::Size) {
|
||||
int v = item->sig->size;
|
||||
if (take_focus) ImGui::SetKeyboardFocusHere();
|
||||
bool changed = ImGui::InputInt("##editor", &v, 1, 100, ImGuiInputTextFlags_AutoSelectAll);
|
||||
if (take_focus) {
|
||||
edit_int_ = item->sig->size;
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
}
|
||||
bool changed = inputInt("##editor", &edit_int_, 1, 100, ImGuiInputTextFlags_AutoSelectAll);
|
||||
if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
|
||||
open_item_ = nullptr; // InputInt already reverted the value; only the commit has to be skipped
|
||||
return;
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit() || (changed && !ImGui::IsItemActive())) {
|
||||
queueCommit(item, std::clamp(v, 1, CAN_MAX_DATA_BYTES));
|
||||
edit_int_ = std::clamp(edit_int_, 1, CAN_MAX_DATA_BYTES);
|
||||
queueCommit(item, edit_int_);
|
||||
}
|
||||
// Enter, Escape and a click outside close the editor; the step buttons keep it open
|
||||
if (ImGui::IsItemDeactivated() && (!ImGui::IsItemHovered() || ImGui::IsKeyPressed(ImGuiKey_Enter, false) ||
|
||||
@@ -492,7 +495,7 @@ void SignalView::drawValueDescriptionDlg() {
|
||||
}
|
||||
|
||||
static ImVec2 indexButtonsSize(float button) {
|
||||
return ImVec2(button * 2 + ImGui::GetStyle().ItemInnerSpacing.x * 2, button);
|
||||
return ImVec2(button * 2 + ImGui::GetStyle().ItemSpacing.x, button);
|
||||
}
|
||||
|
||||
SignalView::SignalView(ChartsWidget *charts) : charts_(charts) {
|
||||
@@ -871,7 +874,7 @@ bool SignalView::drawItem(SignalModel::Item *item, int depth, DrawContext &ctx)
|
||||
}
|
||||
|
||||
void SignalView::drawIndexWidget(SignalModel::Item *item, const ImRect &rect) {
|
||||
const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
const ImVec2 size = indexButtonsSize(iconButtonWidth());
|
||||
ImGui::SetCursorScreenPos(ImVec2(rect.Max.x - size.x, rect.Min.y + (rect.GetHeight() - size.y) * 0.5f));
|
||||
|
||||
@@ -910,12 +913,12 @@ bool ValueDescriptionDlg::draw() {
|
||||
if (!ImGui::BeginPopupModal(popup_id.c_str(), &open, ImGuiWindowFlags_NoSavedSettings)) return ImGui::IsPopupOpen(popup_id.c_str());
|
||||
|
||||
bool closing = false;
|
||||
if (iconButton("add", icon::PLUS_LG, "Add")) {
|
||||
if (stepButton("add", true, "Add")) {
|
||||
table_.emplace_back("", "");
|
||||
}
|
||||
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(current_row_ == -1);
|
||||
if (iconButton("remove", icon::DASH_LG, "Remove") && current_row_ < table_.size()) {
|
||||
if (stepButton("remove", false, "Remove") && current_row_ < table_.size()) {
|
||||
table_.erase(table_.begin() + current_row_);
|
||||
current_row_ = -1;
|
||||
}
|
||||
|
||||
@@ -190,6 +190,7 @@ private:
|
||||
std::function<void()> pending_commit_;
|
||||
SignalModel::Item *editing_item_ = nullptr; // the open text editor
|
||||
std::string edit_text_;
|
||||
int edit_int_ = 0;
|
||||
bool editor_active_ = false; // editor had the keyboard focus last frame
|
||||
bool refocus_editor_ = false; // reopen the editor rejected by the validator
|
||||
bool enter_pressed_ = false;
|
||||
|
||||
@@ -25,8 +25,7 @@ const int MIN_VIDEO_HEIGHT = 100;
|
||||
const int THUMBNAIL_MARGIN = 3;
|
||||
const float POINT_10_FONT_SIZE = 13.0f; // 10 pt at 96 dpi
|
||||
const float POINT_16_FONT_SIZE = 21.0f; // 16 pt at 96 dpi
|
||||
const float TOOLBAR_MARGIN_Y = 6.0f; // between the slider and the buttons, which are as tall as the ones in the charts toolbar
|
||||
const float TOOLBAR_SEPARATOR_EXTENT = 6.0f;
|
||||
constexpr float TOOLBAR_SEPARATOR_EXTENT = 1.0f;
|
||||
const float SLIDER_HEIGHT = 15.0f; // the handle plus a 1 px margin
|
||||
|
||||
// Indexed by TimelineType: None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserBookmark
|
||||
@@ -138,11 +137,11 @@ std::string VideoWidget::whatsThis() const {
|
||||
"Pause/Resume: <span style=\"background-color:lightGray;color:gray\"> space </span>";
|
||||
}
|
||||
|
||||
static float toolbarHeight() { return TOOLBAR_MARGIN_Y + ImGui::GetFrameHeight(); }
|
||||
static float toolbarHeight() { return ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeight(); }
|
||||
|
||||
void VideoWidget::drawPlaybackController() {
|
||||
if (!can->liveStreaming())
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + TOOLBAR_MARGIN_Y);
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().ItemSpacing.y);
|
||||
const float speed_width = menuButtonWidth("0.05x", true);
|
||||
|
||||
const char *play_icon = can->isPaused() ? icon::PLAY : icon::PAUSE;
|
||||
@@ -156,11 +155,11 @@ void VideoWidget::drawPlaybackController() {
|
||||
if (!can->liveStreaming()) {
|
||||
items.push_back(toolbarAction("rewind", icon::REWIND, "Seek backward", []() { can->seekTo(can->currentSec() - 1); }));
|
||||
}
|
||||
items.push_back(toolbarAction("play", play_icon, play_tooltip, []() { can->pause(!can->isPaused()); }, true, true));
|
||||
items.push_back(toolbarAction("play", play_icon, play_tooltip, []() { can->pause(!can->isPaused()); }));
|
||||
if (can->liveStreaming()) {
|
||||
items.push_back(toolbarAction("skip-end", icon::SKIP_END, "Go live", [this]() { skipToEnd(); }, skip_to_end_enabled_, true));
|
||||
items.push_back(toolbarAction("skip-end", icon::SKIP_END, "Go live", [this]() { skipToEnd(); }, skip_to_end_enabled_));
|
||||
} else {
|
||||
items.push_back(toolbarAction("fast-forward", icon::FAST_FORWARD, "Seek forward", []() { can->seekTo(can->currentSec() + 1); }, true, true));
|
||||
items.push_back(toolbarAction("fast-forward", icon::FAST_FORWARD, "Seek forward", []() { can->seekTo(can->currentSec() + 1); }));
|
||||
}
|
||||
if (slider_ || msgs_received_) {
|
||||
// a mono font: with proportional digits the time changed width as it ticked and the items after it moved
|
||||
@@ -186,20 +185,21 @@ void VideoWidget::drawPlaybackController() {
|
||||
const ImVec2 min = ImGui::GetCursorScreenPos();
|
||||
ImGui::Dummy(ImVec2(TOOLBAR_SEPARATOR_EXTENT, ImGui::GetFrameHeight()));
|
||||
const float x = std::floor(min.x + TOOLBAR_SEPARATOR_EXTENT * 0.5f);
|
||||
ImGui::GetWindowDrawList()->AddLine(ImVec2(x, min.y + 4.0f), ImVec2(x, min.y + ImGui::GetFrameHeight() - 4.0f), ImGui::GetColorU32(ImGuiCol_Separator));
|
||||
const float inset = ImGui::GetStyle().FramePadding.y;
|
||||
ImGui::GetWindowDrawList()->AddLine(ImVec2(x, min.y + inset), ImVec2(x, min.y + ImGui::GetFrameHeight() - inset),
|
||||
ImGui::GetColorU32(ImGuiCol_Separator));
|
||||
}};
|
||||
item.in_menu = false;
|
||||
item.tight = true;
|
||||
return item;
|
||||
};
|
||||
const char *aspect_ratio_icon = settings.crop_video ? icon::ASPECT_RATIO_FILL : icon::ASPECT_RATIO;
|
||||
if (!can->liveStreaming()) {
|
||||
items.push_back(toolbarAction("crop_video", aspect_ratio_icon, "Crop to fill", [this]() { cropVideoClicked(); }));
|
||||
items.push_back(separator());
|
||||
items.push_back(toolbarAction("loop", loop_icon, "Loop playback", [this]() { loopPlaybackClicked(); }, true, true));
|
||||
items.push_back(toolbarMenu("speed_btn", speed_text_, "Speed", [this]() { drawSpeedMenuItems(); }, true, true, speed_width));
|
||||
items.push_back(toolbarAction("loop", loop_icon, "Loop playback", [this]() { loopPlaybackClicked(); }));
|
||||
items.push_back(toolbarMenu("speed_btn", speed_text_, "Speed", [this]() { drawSpeedMenuItems(); }, true, speed_width));
|
||||
items.push_back(separator());
|
||||
items.push_back(toolbarAction("route_info", icon::INFO_CIRCLE, "View route details", [this]() { showRouteInfo(); }, true, true));
|
||||
items.push_back(toolbarAction("route_info", icon::INFO_CIRCLE, "View route details", [this]() { showRouteInfo(); }));
|
||||
}
|
||||
|
||||
drawToolbar(items, spacer_index);
|
||||
@@ -275,16 +275,20 @@ void VideoWidget::createCameraWidget() {
|
||||
}
|
||||
|
||||
void VideoWidget::drawCameraWidget() {
|
||||
const float toolbar_height = toolbarHeight();
|
||||
// Camera tabs, video and timeline touch; restore the normal gap for the controls.
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
|
||||
camera_tab_->draw();
|
||||
|
||||
// cam_widget_: minimum height MIN_VIDEO_HEIGHT, takes the space left by the slider and the toolbar
|
||||
const ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||
const float cam_height = std::max((float)MIN_VIDEO_HEIGHT, avail.y - SLIDER_HEIGHT - toolbarHeight());
|
||||
const float cam_height = std::max((float)MIN_VIDEO_HEIGHT, avail.y - SLIDER_HEIGHT - toolbar_height);
|
||||
cam_widget_->draw(ImVec2(avail.x, cam_height), thumbnail_display_time_);
|
||||
|
||||
if (!slider_->isSliderDown()) slider_->setCurrentSecond(can->currentSec());
|
||||
slider_->draw(thumbnail_display_time_);
|
||||
updateSliderThumbnail();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
void VideoWidget::vipcAvailableStreamsUpdated(std::set<VisionStreamType> streams) {
|
||||
@@ -364,12 +368,10 @@ float VideoWidget::defaultHeight(float width) const {
|
||||
}
|
||||
|
||||
void VideoWidget::draw() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
|
||||
if (!can->liveStreaming())
|
||||
drawCameraWidget();
|
||||
|
||||
drawPlaybackController();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
for (auto it = route_info_dlgs_.begin(); it != route_info_dlgs_.end();) {
|
||||
it = (*it)->draw() ? it + 1 : route_info_dlgs_.erase(it);
|
||||
|
||||
Reference in New Issue
Block a user