From fa47048e4426ad828c4b1d88c855e976659db533 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 19 Jan 2023 05:19:23 +0800 Subject: [PATCH] cabana: use bootstrap icons (#26981) * use bootstrap icons * typo * build into asset_obj * add to files_common old-commit-hash: c21d9408a191ba8e3303f2459d1f7476fefd35ba --- SConstruct | 2 +- release/files_common | 1 + selfdrive/assets/assets.qrc | 1 + selfdrive/ui/SConscript | 1 + selfdrive/ui/qt/util.cc | 37 ++++++++++++++++++++++++++++++++++++ selfdrive/ui/qt/util.h | 1 + tools/cabana/SConscript | 4 ++-- tools/cabana/chartswidget.cc | 13 ++++++++----- tools/cabana/detailwidget.cc | 5 +++-- tools/cabana/signaledit.cc | 6 ++++-- tools/cabana/videowidget.cc | 15 +++++++++++---- tools/cabana/videowidget.h | 1 + 12 files changed, 71 insertions(+), 16 deletions(-) diff --git a/SConstruct b/SConstruct index 3051f4298d..8864164f15 100644 --- a/SConstruct +++ b/SConstruct @@ -282,7 +282,7 @@ Export('envCython') # Qt build environment qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", "Quick", "Qml", "QuickWidgets", "Location", "Positioning", "DBus"] +qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", "Quick", "Qml", "QuickWidgets", "Location", "Positioning", "DBus", "Xml"] qt_libs = [] if arch == "Darwin": diff --git a/release/files_common b/release/files_common index 3d07924a91..0dc0e96226 100644 --- a/release/files_common +++ b/release/files_common @@ -436,6 +436,7 @@ third_party/acados/larch64/** third_party/acados/include/** third_party/acados/acados_template/** +third_party/bootstrap/** third_party/qt5/larch64/bin/** scripts/update_now.sh diff --git a/selfdrive/assets/assets.qrc b/selfdrive/assets/assets.qrc index 39be41aa65..79a1a1e272 100644 --- a/selfdrive/assets/assets.qrc +++ b/selfdrive/assets/assets.qrc @@ -1,5 +1,6 @@ + ../../third_party/bootstrap/bootstrap-icons.svg img_continue_triangle.svg img_circled_check.svg img_circled_slash.svg diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 669c214746..3ce7a0505d 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -41,6 +41,7 @@ assets_src = "#selfdrive/assets/assets.qrc" qt_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, "#selfdrive/assets/assets.o"])) asset_obj = qt_env.Object("assets", assets) +Export('asset_obj') # build soundd qt_env.Program("soundd/_soundd", ["soundd/main.cc", "soundd/sound.cc"], LIBS=qt_libs) diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 59903e3376..4f52310649 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -2,11 +2,14 @@ #include #include +#include #include #include #include #include #include +#include +#include #include "common/params.h" #include "common/swaglog.h" @@ -218,3 +221,37 @@ QColor interpColor(float xv, std::vector xp, std::vector fp) { ); } } + +static QHash load_bootstrap_icons() { + QHash icons; + + QFile f(":/bootstrap-icons.svg"); + if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { + QDomDocument xml; + xml.setContent(&f); + QDomNode n = xml.documentElement().firstChild(); + while (!n.isNull()) { + QDomElement e = n.toElement(); + if (!e.isNull() && e.hasAttribute("id")) { + QString svg_str; + QTextStream stream(&svg_str); + n.save(stream, 0); + svg_str.replace("", ""); + icons[e.attribute("id")] = svg_str.toUtf8(); + } + n = n.nextSibling(); + } + } + return icons; +} + +QPixmap bootstrapPixmap(const QString &id) { + static QHash icons = load_bootstrap_icons(); + + QPixmap pixmap; + if (auto it = icons.find(id); it != icons.end()) { + pixmap.loadFromData(it.value(), "svg"); + } + return pixmap; +} diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 61a27a8669..3188f3f9b9 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -22,6 +22,7 @@ void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, co void initApp(int argc, char *argv[]); QWidget* topWidget (QWidget* widget); QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); +QPixmap bootstrapPixmap(const QString &id); QRect getTextRect(QPainter &p, int flags, const QString &text); void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom); diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 507b98ae88..6726a042ad 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -1,6 +1,6 @@ import os Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', - 'cereal', 'transformations', 'widgets', 'opendbc') + 'cereal', 'transformations', 'widgets', 'opendbc', 'asset_obj') base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', @@ -22,7 +22,7 @@ prev_moc_path = cabana_env['QT_MOCHPREFIX'] cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', 'canmessages.cc', 'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) -cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, asset_obj], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('test'): cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 33e133d140..69229baa64 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -12,6 +12,8 @@ #include #include +#include "selfdrive/ui/qt/util.h" + // ChartsWidget ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { @@ -19,14 +21,15 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { // toolbar QToolBar *toolbar = new QToolBar(tr("Charts"), this); + toolbar->setIconSize({16, 16}); title_label = new QLabel(); title_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); toolbar->addWidget(title_label); show_all_values_btn = toolbar->addAction(""); toolbar->addWidget(range_label = new QLabel()); - reset_zoom_btn = toolbar->addAction("⟲"); + reset_zoom_btn = toolbar->addAction(bootstrapPixmap("arrow-counterclockwise"), ""); reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); - remove_all_btn = toolbar->addAction("✖"); + remove_all_btn = toolbar->addAction(bootstrapPixmap("x"), ""); remove_all_btn->setToolTip(tr("Remove all charts")); dock_btn = toolbar->addAction(""); main_layout->addWidget(toolbar); @@ -137,7 +140,7 @@ void ChartsWidget::updateToolBar() { reset_zoom_btn->setEnabled(is_zoomed); range_label->setText(is_zoomed ? tr("%1 - %2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2) : ""); title_label->setText(charts.size() > 0 ? tr("Charts (%1)").arg(charts.size()) : tr("Charts")); - dock_btn->setText(docking ? "⬈" : "⬋"); + dock_btn->setIcon(bootstrapPixmap(docking ? "arrow-up-right" : "arrow-down-left")); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } @@ -223,7 +226,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { chart->layout()->setContentsMargins(0, 0, 0, 0); QToolButton *remove_btn = new QToolButton(); - remove_btn->setText("X"); + remove_btn->setIcon(bootstrapPixmap("x")); remove_btn->setAutoRaise(true); remove_btn->setToolTip(tr("Remove Chart")); close_btn_proxy = new QGraphicsProxyWidget(chart); @@ -231,7 +234,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { close_btn_proxy->setZValue(chart->zValue() + 11); QToolButton *manage_btn = new QToolButton(); - manage_btn->setText("🔧"); + manage_btn->setIcon(bootstrapPixmap("gear")); manage_btn->setAutoRaise(true); manage_btn->setToolTip(tr("Manage series")); manage_btn_proxy = new QGraphicsProxyWidget(chart); diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 06377616da..247111aaf4 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -36,6 +36,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart // message title toolbar = new QToolBar(this); + toolbar->setIconSize({16, 16}); toolbar->addWidget(new QLabel("time:")); time_label = new QLabel(this); time_label->setStyleSheet("font-weight:bold"); @@ -45,8 +46,8 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart name_label->setAlignment(Qt::AlignCenter); name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); toolbar->addWidget(name_label); - toolbar->addAction("🖍", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message")); - remove_msg_act = toolbar->addAction("X", this, &DetailWidget::removeMsg); + toolbar->addAction(bootstrapPixmap("pencil"), "", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message")); + remove_msg_act = toolbar->addAction(bootstrapPixmap("x-lg"), "", this, &DetailWidget::removeMsg); remove_msg_act->setToolTip(tr("Remove Message")); toolbar->setVisible(false); frame_layout->addWidget(toolbar); diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 2a92e03461..e2025dcc82 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -6,6 +6,8 @@ #include #include +#include "selfdrive/ui/qt/util.h" + // SignalForm SignalForm::SignalForm(QWidget *parent) : QWidget(parent) { @@ -104,13 +106,13 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa title_layout->addWidget(title); plot_btn = new QToolButton(this); - plot_btn->setText("📈"); + plot_btn->setIcon(bootstrapPixmap("graph-up")); plot_btn->setCheckable(true); plot_btn->setAutoRaise(true); title_layout->addWidget(plot_btn); auto remove_btn = new QToolButton(this); remove_btn->setAutoRaise(true); - remove_btn->setText("x"); + remove_btn->setIcon(bootstrapPixmap("x")); remove_btn->setToolTip(tr("Remove signal")); title_layout->addWidget(remove_btn); main_layout->addWidget(title_bar); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 99d82daccf..400cc5108a 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -13,6 +13,8 @@ #include #include +#include "selfdrive/ui/qt/util.h" + inline QString formatTime(int seconds) { return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); } @@ -43,8 +45,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { // btn controls QHBoxLayout *control_layout = new QHBoxLayout(); - play_btn = new QPushButton("⏸"); - play_btn->setStyleSheet("font-weight:bold; height:16px"); + play_btn = new QPushButton(); control_layout->addWidget(play_btn); QButtonGroup *group = new QButtonGroup(this); @@ -68,12 +69,13 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(play_btn, &QPushButton::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(can, &CANMessages::updated, this, &VideoWidget::updateState); - QObject::connect(can, &CANMessages::paused, [this]() { play_btn->setText("▶"); }); - QObject::connect(can, &CANMessages::resume, [this]() { play_btn->setText("⏸"); }); + QObject::connect(can, &CANMessages::paused, this, &VideoWidget::updatePlayBtnState); + QObject::connect(can, &CANMessages::resume, this, &VideoWidget::updatePlayBtnState); QObject::connect(can, &CANMessages::streamStarted, [this]() { end_time_label->setText(formatTime(can->totalSeconds())); slider->setRange(0, can->totalSeconds() * 1000); }); + updatePlayBtnState(); } void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { @@ -90,6 +92,11 @@ void VideoWidget::updateState() { slider->setValue(can->currentSec() * 1000); } +void VideoWidget::updatePlayBtnState() { + play_btn->setIcon(bootstrapPixmap(can->isPaused() ? "play" : "pause")); + play_btn->setToolTip(can->isPaused() ? tr("Play") : tr("Pause")); +} + // Slider Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { QTimer *timer = new QTimer(this); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 515dab6c87..424a5b08fe 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -44,6 +44,7 @@ public: protected: void updateState(); + void updatePlayBtnState(); CameraWidget *cam_widget; QLabel *end_time_label;