diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index ec39ac470c..eb0af5b64a 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -437,7 +437,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->fillRect(option.rect, item->bg_color); } auto color_role = item->sigs.contains(bin_view->hovered_sig) ? QPalette::BrightText : QPalette::Text; - painter->setPen(option.palette.color(color_role)); + painter->setPen(option.palette.color(bin_view->is_message_active ? QPalette::Normal : QPalette::Disabled, color_role)); } if (item->sigs.size() > 1) { diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 3802bf14d4..920deb0018 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -69,7 +69,11 @@ public: void setMessage(const MessageId &message_id); void highlight(const cabana::Signal *sig); QSet getOverlappingSignals() const; - inline void updateState() { model->updateState(); } + void updateState() { model->updateState(); } + void paintEvent(QPaintEvent *event) override { + is_message_active = can->isMessageActive(model->msg_id); + QTableView::paintEvent(event); + } QSize minimumSizeHint() const override; void setHeatmapLiveMode(bool live) { model->heatmap_live_mode = live; updateState(); } @@ -93,6 +97,7 @@ private: QModelIndex anchor_index; BinaryViewModel *model; BinaryItemDelegate *delegate; + bool is_message_active = false; const cabana::Signal *resize_sig = nullptr; const cabana::Signal *hovered_sig = nullptr; friend class BinaryItemDelegate; diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 6c13252e80..3588c5edc6 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -842,6 +842,8 @@ void ChartView::setSeriesType(SeriesType type) { } updateSeriesPoints(); updateTitle(); + + menu->actions()[(int)type]->setChecked(true); } } diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index e2f0d4d0c9..5f16eeafd0 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -23,7 +23,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { main_layout->setSpacing(0); // toolbar - QToolBar *toolbar = new QToolBar(tr("Charts"), this); + toolbar = new QToolBar(tr("Charts"), this); int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize); toolbar->setIconSize({icon_size, icon_size}); @@ -34,6 +34,21 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { toolbar->addWidget(title_label = new QLabel()); title_label->setContentsMargins(0, 0, style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0); + auto chart_type_action = toolbar->addAction(""); + QMenu *chart_type_menu = new QMenu(this); + auto types = std::array{tr("Line"), tr("Step"), tr("Scatter")}; + for (int i = 0; i < types.size(); ++i) { + QString type_text = types[i]; + chart_type_menu->addAction(type_text, this, [=]() { + settings.chart_series_type = i; + chart_type_action->setText("Type: " + type_text); + settingChanged(); + }); + } + chart_type_action->setText("Type: " + types[settings.chart_series_type]); + chart_type_action->setMenu(chart_type_menu); + qobject_cast(toolbar->widgetForAction(chart_type_action))->setPopupMode(QToolButton::InstantPopup); + QMenu *menu = new QMenu(this); for (int i = 0; i < MAX_COLUMN_COUNT; ++i) { menu->addAction(tr("%1").arg(i + 1), [=]() { setColumnCount(i + 1); }); @@ -42,13 +57,13 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { columns_action->setMenu(menu); qobject_cast(toolbar->widgetForAction(columns_action))->setPopupMode(QToolButton::InstantPopup); - QLabel *stretch_label = new QLabel(this); - stretch_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - toolbar->addWidget(stretch_label); + QWidget *spacer = new QWidget(this); + spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + toolbar->addWidget(spacer); range_lb_action = toolbar->addWidget(range_lb = new QLabel(this)); range_slider = new LogSlider(1000, Qt::Horizontal, this); - range_slider->setMaximumWidth(200); + range_slider->setFixedWidth(150 * qApp->devicePixelRatio()); range_slider->setToolTip(tr("Set the chart range")); range_slider->setRange(1, settings.max_cached_minutes * 60); range_slider->setSingleStep(1); @@ -121,10 +136,12 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { setIsDocked(true); newTab(); qApp->installEventFilter(this); - setWhatsThis(tr(R"( - Chart view
- + Chart View
+ Click: Click to seek to a corresponding time.
+ Drag: Zoom into the chart.
+ Shift + Drag: Scrub through the chart to view values.
+ Right Mouse: Open the context menu.
)")); } @@ -219,7 +236,7 @@ void ChartsWidget::setIsDocked(bool docked) { void ChartsWidget::updateToolBar() { title_label->setText(tr("Charts: %1").arg(charts.size())); - columns_action->setText(tr("Column: %1").arg(column_count)); + columns_action->setText(tr("Columns: %1").arg(column_count)); range_lb->setText(utils::formatSeconds(max_chart_range)); bool is_zoomed = can->timeRange().has_value(); @@ -241,7 +258,9 @@ void ChartsWidget::settingChanged() { c->setTheme(theme); } } - range_slider->setRange(1, settings.max_cached_minutes * 60); + if (range_slider->maximum() != settings.max_cached_minutes * 60) { + range_slider->setRange(1, settings.max_cached_minutes * 60); + } for (auto c : charts) { c->setFixedHeight(settings.chart_height); c->setSeriesType((SeriesType)settings.chart_series_type); @@ -380,7 +399,7 @@ void ChartsWidget::doAutoScroll() { } QSize ChartsWidget::minimumSizeHint() const { - return QSize(CHART_MIN_WIDTH, QWidget::minimumSizeHint().height()); + return QSize(CHART_MIN_WIDTH * 1.5 * qApp->devicePixelRatio(), QWidget::minimumSizeHint().height()); } void ChartsWidget::newChart() { diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index bfdaaaa954..20363ebeaf 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -88,6 +89,7 @@ private: bool is_docked = true; ToolButton *dock_btn; + QToolBar *toolbar; QAction *undo_zoom_action; QAction *redo_zoom_action; QAction *reset_zoom_action; diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index da44319b5c..d2b25bc5f2 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -8,16 +8,16 @@ #include #include - const QString UNTITLED = "untitled"; const QString DEFAULT_NODE_NAME = "XXX"; +constexpr int CAN_MAX_DATA_BYTES = 64; struct MessageId { uint8_t source = 0; uint32_t address = 0; QString toString() const { - return QString("%1:%2").arg(source).arg(address, 1, 16); + return QString("%1:%2").arg(source).arg(QString::number(address, 16).toUpper()); } bool operator==(const MessageId &other) const { diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index ae9d12e862..2e213988bc 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -203,8 +203,7 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit name_edit->setValidator(new NameValidator(name_edit)); form_layout->addRow(tr("Size"), size_spin = new QSpinBox(this)); - // TODO: limit the maximum? - size_spin->setMinimum(1); + size_spin->setRange(1, CAN_MAX_DATA_BYTES); size_spin->setValue(size); form_layout->addRow(tr("Node"), node = new QLineEdit(this)); diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index ecb739a733..ed9aeaf311 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -13,21 +13,6 @@ #include "tools/cabana/commands.h" -static bool isMessageActive(const MessageId &id) { - if (id.source == INVALID_SOURCE) { - return false; - } - // Check if the message is active based on time difference and frequency - const auto &m = can->lastMessage(id); - float delta = can->currentSec() - m.ts; - - if (m.freq < std::numeric_limits::epsilon()) { - return delta < 1.5; - } - - return delta < (5.0 / m.freq) + (1.0 / settings.fps); -} - MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); @@ -207,7 +192,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { switch (index.column()) { case Column::NAME: return item.name; case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : NA; - case Column::ADDRESS: return QString::number(item.id.address, 16); + case Column::ADDRESS: return toHexString(item.id.address); case Column::NODE: return item.node; case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(can->lastMessage(item.id).freq) : NA; case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(can->lastMessage(item.id).count) : NA; @@ -300,7 +285,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) { match = parseRange(txt, item.id.source); break; case Column::ADDRESS: - match = QString::number(item.id.address, 16).contains(txt, Qt::CaseInsensitive); + match = toHexString(item.id.address).contains(txt, Qt::CaseInsensitive); match = match || parseRange(txt, item.id.address, 16); break; case Column::NODE: @@ -335,7 +320,7 @@ bool MessageListModel::filterAndSort() { std::vector items; items.reserve(all_messages.size()); for (const auto &id : all_messages) { - if (show_inactive_messages || isMessageActive(id)) { + if (show_inactive_messages || can->isMessageActive(id)) { auto msg = dbc()->msg(id); Item item = {.id = id, .name = msg ? msg->name : UNTITLED, @@ -378,7 +363,7 @@ void MessageListModel::sort(int column, Qt::SortOrder order) { void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { const auto &item = ((MessageListModel*)model())->items_[index.row()]; - if (!isMessageActive(item.id)) { + if (!can->isMessageActive(item.id)) { QStyleOptionViewItem custom_option = option; custom_option.palette.setBrush(QPalette::Text, custom_option.palette.color(QPalette::Disabled, QPalette::Text)); auto color = QApplication::palette().color(QPalette::HighlightedText); diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index 523cbf3be7..cccc9b6d9a 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -89,10 +89,6 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { groupbox = new QGroupBox("Chart"); form_layout = new QFormLayout(groupbox); - form_layout->addRow(tr("Default Series Type"), chart_series_type = new QComboBox(this)); - chart_series_type->addItems({tr("Line"), tr("Step Line"), tr("Scatter")}); - chart_series_type->setCurrentIndex(settings.chart_series_type); - form_layout->addRow(tr("Chart Height"), chart_height = new QSpinBox(this)); chart_height->setRange(100, 500); chart_height->setSingleStep(10); @@ -132,7 +128,6 @@ void SettingsDlg::save() { } settings.fps = fps->value(); settings.max_cached_minutes = cached_minutes->value(); - settings.chart_series_type = chart_series_type->currentIndex(); settings.chart_height = chart_height->value(); settings.log_livestream = log_livestream->isChecked(); settings.log_path = log_path->text(); diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index 130b76d502..9fe70c3ee8 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -382,7 +382,7 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie } else if (item->type == SignalModel::Item::Size) { QSpinBox *spin = new QSpinBox(parent); spin->setFrame(false); - spin->setRange(1, 64); + spin->setRange(1, CAN_MAX_DATA_BYTES); return spin; } else if (item->type == SignalModel::Item::SignalType) { QComboBox *c = new QComboBox(parent); diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index fa71beb2ea..ad911b7bae 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -127,6 +127,21 @@ const CanData &AbstractStream::lastMessage(const MessageId &id) const { return it != last_msgs.end() ? it->second : empty_data; } +bool AbstractStream::isMessageActive(const MessageId &id) const { + if (id.source == INVALID_SOURCE) { + return false; + } + // Check if the message is active based on time difference and frequency + const auto &m = lastMessage(id); + float delta = currentSec() - m.ts; + + if (m.freq < std::numeric_limits::epsilon()) { + return delta < 1.5; + } + + return delta < (5.0 / m.freq) + (1.0 / settings.fps); +} + void AbstractStream::updateLastMsgsTo(double sec) { std::lock_guard lk(mutex_); current_sec_ = sec; diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 2824199161..8180c6b6f8 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -82,6 +82,7 @@ public: inline double toSeconds(uint64_t mono_time) const { return std::max(0.0, (mono_time - beginMonoTime()) / 1e9); } inline const std::unordered_map &lastMessages() const { return last_msgs; } + bool isMessageActive(const MessageId &id) const; inline const MessageEventsMap &eventsMap() const { return events_; } inline const std::vector &allEvents() const { return all_events_; } const CanData &lastMessage(const MessageId &id) const; diff --git a/tools/cabana/utils/util.h b/tools/cabana/utils/util.h index 0ebd7e41f8..1aad3103db 100644 --- a/tools/cabana/utils/util.h +++ b/tools/cabana/utils/util.h @@ -163,3 +163,4 @@ private: int num_decimals(double num); QString signalToolTip(const cabana::Signal *sig); +inline QString toHexString(int value) { return QString("0x%1").arg(QString::number(value, 16).toUpper(), 2, '0'); } diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 0b2beb1dd6..b3afad41ed 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -31,9 +31,12 @@ static Replay *getReplay() { VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { setFrameStyle(QFrame::StyledPanel | QFrame::Plain); auto main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); if (!can->liveStreaming()) main_layout->addWidget(createCameraWidget()); - main_layout->addLayout(createPlaybackController()); + + createPlaybackController(); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); @@ -65,15 +68,19 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { timeline_colors[(int)TimelineType::AlertCritical].name())); } -QHBoxLayout *VideoWidget::createPlaybackController() { - QHBoxLayout *layout = new QHBoxLayout(); - layout->addWidget(seek_backward_btn = new ToolButton("rewind", tr("Seek backward"))); - layout->addWidget(play_btn = new ToolButton("play", tr("Play"))); - layout->addWidget(seek_forward_btn = new ToolButton("fast-forward", tr("Seek forward"))); +void VideoWidget::createPlaybackController() { + QToolBar *toolbar = new QToolBar(this); + layout()->addWidget(toolbar); + + int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize); + toolbar->setIconSize({icon_size, icon_size}); + + toolbar->addAction(utils::icon("rewind"), tr("Seek backward"), []() { can->seekTo(can->currentSec() - 1); }); + play_toggle_action = toolbar->addAction(utils::icon("play"), tr("Play"), []() { can->pause(!can->isPaused()); }); + toolbar->addAction(utils::icon("fast-forward"), tr("Seek forward"), []() { can->seekTo(can->currentSec() + 1); }); if (can->liveStreaming()) { - layout->addWidget(skip_to_end_btn = new ToolButton("skip-end", tr("Skip to the end"), this)); - QObject::connect(skip_to_end_btn, &QToolButton::clicked, [this]() { + skip_to_end_action = toolbar->addAction(utils::icon("skip-end"), tr("Skip to the end"), this, [this]() { // set speed to 1.0 speed_btn->menu()->actions()[7]->setChecked(true); can->pause(false); @@ -81,53 +88,48 @@ QHBoxLayout *VideoWidget::createPlaybackController() { }); } - layout->addWidget(time_btn = new QToolButton); - time_btn->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time")); - time_btn->setAutoRaise(true); - layout->addStretch(0); + time_display_action = toolbar->addAction("", this, [this]() { + settings.absolute_time = !settings.absolute_time; + time_display_action->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time")); + updateState(); + }); + + QWidget *spacer = new QWidget(); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + toolbar->addWidget(spacer); if (!can->liveStreaming()) { - layout->addWidget(loop_btn = new ToolButton("repeat", tr("Loop playback"))); - QObject::connect(loop_btn, &QToolButton::clicked, this, &VideoWidget::loopPlaybackClicked); + toolbar->addAction(utils::icon("repeat"), tr("Loop playback"), this, &VideoWidget::loopPlaybackClicked); } - // speed selector - layout->addWidget(speed_btn = new QToolButton(this)); - speed_btn->setAutoRaise(true); + createSpeedDropdown(toolbar); +} + +void VideoWidget::createSpeedDropdown(QToolBar *toolbar) { + toolbar->addWidget(speed_btn = new QToolButton(this)); speed_btn->setMenu(new QMenu(speed_btn)); speed_btn->setPopupMode(QToolButton::InstantPopup); QActionGroup *speed_group = new QActionGroup(this); speed_group->setExclusive(true); - int max_width = 0; - QFont font = speed_btn->font(); - font.setBold(true); - speed_btn->setFont(font); - QFontMetrics fm(font); for (float speed : {0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 0.8, 1., 2., 3., 5.}) { - QString name = QString("%1x").arg(speed); - max_width = std::max(max_width, fm.width(name) + fm.horizontalAdvance(QLatin1Char(' ')) * 2); - - QAction *act = new QAction(name, speed_group); - act->setCheckable(true); - QObject::connect(act, &QAction::toggled, [this, speed]() { + auto act = speed_btn->menu()->addAction(QString("%1x").arg(speed), this, [this, speed]() { can->setSpeed(speed); speed_btn->setText(QString("%1x ").arg(speed)); }); - speed_btn->menu()->addAction(act); - if (speed == 1.0)act->setChecked(true); + + speed_group->addAction(act); + act->setCheckable(true); + if (speed == 1.0) { + act->setChecked(true); + act->trigger(); + } } - speed_btn->setMinimumWidth(max_width + style()->pixelMetric(QStyle::PM_MenuButtonIndicator)); - QObject::connect(play_btn, &QToolButton::clicked, []() { can->pause(!can->isPaused()); }); - QObject::connect(seek_backward_btn, &QToolButton::clicked, []() { can->seekTo(can->currentSec() - 1); }); - QObject::connect(seek_forward_btn, &QToolButton::clicked, []() { can->seekTo(can->currentSec() + 1); }); - QObject::connect(time_btn, &QToolButton::clicked, [this]() { - settings.absolute_time = !settings.absolute_time; - time_btn->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time")); - updateState(); - }); - return layout; + QFont font = speed_btn->font(); + font.setBold(true); + speed_btn->setFont(font); + speed_btn->setMinimumWidth(speed_btn->fontMetrics().width("0.05x ") + style()->pixelMetric(QStyle::PM_MenuButtonIndicator)); } QWidget *VideoWidget::createCameraWidget() { @@ -180,13 +182,13 @@ void VideoWidget::vipcAvailableStreamsUpdated(std::set streams void VideoWidget::loopPlaybackClicked() { bool is_looping = getReplay()->loop(); getReplay()->setLoop(!is_looping); - loop_btn->setIcon(!is_looping ? "repeat" : "repeat-1"); + qobject_cast(sender())->setIcon(utils::icon(!is_looping ? "repeat" : "repeat-1")); } void VideoWidget::timeRangeChanged() { const auto time_range = can->timeRange(); if (can->liveStreaming()) { - skip_to_end_btn->setEnabled(!time_range.has_value()); + skip_to_end_action->setEnabled(!time_range.has_value()); return; } time_range ? slider->setTimeRange(time_range->first, time_range->second) @@ -208,16 +210,16 @@ void VideoWidget::updateState() { if (camera_tab->count() == 0) { // No streams available cam_widget->update(); // Manually refresh to show alert events } - time_btn->setText(QString("%1 / %2").arg(formatTime(can->currentSec(), true), + time_display_action->setText(QString("%1 / %2").arg(formatTime(can->currentSec(), true), formatTime(slider->maximum() / slider->factor))); } else { - time_btn->setText(formatTime(can->currentSec(), true)); + time_display_action->setText(formatTime(can->currentSec(), true)); } } void VideoWidget::updatePlayBtnState() { - play_btn->setIcon(can->isPaused() ? "play" : "pause"); - play_btn->setToolTip(can->isPaused() ? tr("Play") : tr("Pause")); + play_toggle_action->setIcon(utils::icon(can->isPaused() ? "play" : "pause")); + play_toggle_action->setToolTip(can->isPaused() ? tr("Play") : tr("Pause")); } // Slider @@ -228,8 +230,12 @@ Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { void Slider::paintEvent(QPaintEvent *ev) { QPainter p(this); - QRect r = rect().adjusted(0, 4, 0, -4); + + QStyleOptionSlider opt; + initStyleOption(&opt); + QRect r = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); p.fillRect(r, timeline_colors[(int)TimelineType::None]); + double min = minimum() / factor; double max = maximum() / factor; @@ -254,8 +260,6 @@ void Slider::paintEvent(QPaintEvent *ev) { } } - QStyleOptionSlider opt; - opt.initFrom(this); opt.minimum = minimum(); opt.maximum = maximum(); opt.subControls = QStyle::SC_SliderHandle; diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 78503365e5..d00657b164 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -6,10 +6,10 @@ #include #include -#include #include #include #include +#include #include #include "selfdrive/ui/qt/widgets/cameraview.h" @@ -62,18 +62,16 @@ protected: void updateState(); void updatePlayBtnState(); QWidget *createCameraWidget(); - QHBoxLayout *createPlaybackController(); + void createPlaybackController(); + void createSpeedDropdown(QToolBar *toolbar); void loopPlaybackClicked(); void vipcAvailableStreamsUpdated(std::set streams); StreamCameraView *cam_widget; - QToolButton *time_btn = nullptr; - ToolButton *seek_backward_btn = nullptr; - ToolButton *play_btn = nullptr; - ToolButton *seek_forward_btn = nullptr; - ToolButton *loop_btn = nullptr; + QAction *time_display_action = nullptr; + QAction *play_toggle_action = nullptr; QToolButton *speed_btn = nullptr; - ToolButton *skip_to_end_btn = nullptr; + QAction *skip_to_end_action = nullptr; Slider *slider = nullptr; QTabBar *camera_tab = nullptr; };