diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 5f16eeafd05b95..08840f46633f10 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -193,6 +193,7 @@ QRect ChartsWidget::chartVisibleRect(ChartView *chart) { } void ChartsWidget::showValueTip(double sec) { + emit showTip(sec); if (sec < 0 && !value_tip_visible_) return; value_tip_visible_ = sec >= 0; @@ -548,20 +549,14 @@ void ChartsContainer::dropEvent(QDropEvent *event) { void ChartsContainer::paintEvent(QPaintEvent *ev) { if (!drop_indictor_pos.isNull() && !childAt(drop_indictor_pos)) { - QRect r; + QRect r = geometry(); + r.setHeight(CHART_SPACING); if (auto insert_after = getDropAfter(drop_indictor_pos)) { - QRect area = insert_after->geometry(); - r = QRect(area.left(), area.bottom() + 1, area.width(), CHART_SPACING); - } else { - r = geometry(); - r.setHeight(CHART_SPACING); + r.moveTop(insert_after->geometry().bottom()); } QPainter p(this); - p.setPen(QPen(palette().highlight(), 2)); - p.drawLine(r.topLeft() + QPoint(1, 0), r.bottomLeft() + QPoint(1, 0)); - p.drawLine(r.topLeft() + QPoint(0, r.height() / 2), r.topRight() + QPoint(0, r.height() / 2)); - p.drawLine(r.topRight(), r.bottomRight()); + p.fillRect(r, palette().highlight()); } } diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index 20363ebeafa4b0..46e7f546b02e37 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -53,6 +53,7 @@ public slots: signals: void toggleChartsDocking(); void seriesChanged(); + void showTip(double seconds); private: QSize minimumSizeHint() const override; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index d3f868cd47fdb1..8361befb53aa08 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -191,6 +191,7 @@ void MainWindow::createDockWidgets() { video_splitter->handle(1)->setEnabled(!can->liveStreaming()); video_dock->setWidget(video_splitter); QObject::connect(charts_widget, &ChartsWidget::toggleChartsDocking, this, &MainWindow::toggleChartsDocking); + QObject::connect(charts_widget, &ChartsWidget::showTip, video_widget, &VideoWidget::showThumbnail); } void MainWindow::createStatusBar() { diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index b3afad41edddaf..97e08d6afd625f 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -152,7 +152,6 @@ QWidget *VideoWidget::createCameraWidget() { QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); }); QObject::connect(can, &AbstractStream::paused, cam_widget, [c = cam_widget]() { c->showPausedOverlay(); }); - QObject::connect(can, &AbstractStream::resume, cam_widget, [c = cam_widget]() { c->update(); }); QObject::connect(can, &AbstractStream::eventsMerged, this, [this]() { slider->update(); }); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(cam_widget, &CameraWidget::vipcAvailableStreamsUpdated, this, &VideoWidget::vipcAvailableStreamsUpdated); @@ -160,7 +159,7 @@ QWidget *VideoWidget::createCameraWidget() { if (index != -1) cam_widget->setStreamType((VisionStreamType)camera_tab->tabData(index).toInt()); }); QObject::connect(static_cast(can), &ReplayStream::qLogLoaded, cam_widget, &StreamCameraView::parseQLog, Qt::QueuedConnection); - slider->installEventFilter(cam_widget); + slider->installEventFilter(this); return w; } @@ -222,8 +221,24 @@ void VideoWidget::updatePlayBtnState() { play_toggle_action->setToolTip(can->isPaused() ? tr("Play") : tr("Pause")); } -// Slider +void VideoWidget::showThumbnail(double seconds) { + cam_widget->thumbnail_dispaly_time = seconds; + slider->thumbnail_dispaly_time = seconds; + cam_widget->update(); + slider->update(); +} + +bool VideoWidget::eventFilter(QObject *obj, QEvent *event) { + if (event->type() == QEvent::MouseMove) { + auto [min_sec, max_sec] = can->timeRange().value_or(std::make_pair(can->minSeconds(), can->maxSeconds())); + showThumbnail(min_sec + static_cast(event)->pos().x() * (max_sec - min_sec) / slider->width()); + } else if (event->type() == QEvent::Leave) { + showThumbnail(-1); + } + return false; +} +// Slider Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { setMouseTracking(true); } @@ -265,6 +280,14 @@ void Slider::paintEvent(QPaintEvent *ev) { opt.subControls = QStyle::SC_SliderHandle; opt.sliderPosition = value(); style()->drawComplexControl(QStyle::CC_Slider, &opt, &p); + + if (thumbnail_dispaly_time >= 0) { + int left = (thumbnail_dispaly_time - min) * width() / (max - min) - 1; + QRect rc(left, rect().top() + 1, 2, rect().height() - 2); + p.setBrush(palette().highlight()); + p.setPen(Qt::NoPen); + p.drawRoundedRect(rc, 1.5, 1.5); + } } void Slider::mousePressEvent(QMouseEvent *e) { @@ -276,7 +299,6 @@ void Slider::mousePressEvent(QMouseEvent *e) { } // StreamCameraView - StreamCameraView::StreamCameraView(std::string stream_name, VisionStreamType stream_type, QWidget *parent) : CameraWidget(stream_name, stream_type, parent) { fade_animation = new QPropertyAnimation(this, "overlayOpacity"); @@ -298,6 +320,7 @@ void StreamCameraView::parseQLog(std::shared_ptr qlog) { QPixmap generated_thumb = generateThumbnail(thumb, can->toSeconds(thumb_data.getTimestampEof())); std::lock_guard lock(mutex); thumbnails[thumb_data.getTimestampEof()] = generated_thumb; + big_thumbnails[thumb_data.getTimestampEof()] = thumb; } } }); @@ -308,12 +331,15 @@ void StreamCameraView::paintGL() { CameraWidget::paintGL(); QPainter p(this); - if (auto alert = getReplay()->findAlertAtTime(can->currentSec())) { - drawAlert(p, rect(), *alert); + bool scrubbing = false; + if (thumbnail_dispaly_time >= 0) { + scrubbing = can->isPaused(); + scrubbing ? drawScrubThumbnail(p) : drawThumbnail(p); } - if (thumbnail_pt_) { - drawThumbnail(p); + if (auto alert = getReplay()->findAlertAtTime(scrubbing ? thumbnail_dispaly_time : can->currentSec())) { + drawAlert(p, rect(), *alert); } + if (can->isPaused()) { p.setPen(QColor(200, 200, 200, static_cast(255 * fade_animation->currentValue().toFloat()))); p.setFont(QFont(font().family(), 16, QFont::Bold)); @@ -333,25 +359,37 @@ QPixmap StreamCameraView::generateThumbnail(QPixmap thumb, double seconds) { return scaled; } -void StreamCameraView::drawThumbnail(QPainter &p) { - int pos = std::clamp(thumbnail_pt_->x(), 0, width()); - auto [min_sec, max_sec] = can->timeRange().value_or(std::make_pair(can->minSeconds(), can->maxSeconds())); - double seconds = min_sec + pos * (max_sec - min_sec) / width(); +void StreamCameraView::drawScrubThumbnail(QPainter &p) { + p.fillRect(rect(), Qt::black); + auto it = big_thumbnails.lowerBound(can->toMonoTime(thumbnail_dispaly_time)); + if (it != big_thumbnails.end()) { + QPixmap scaled_thumb = it.value().scaled(rect().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + QRect thumb_rect(rect().center() - scaled_thumb.rect().center(), scaled_thumb.size()); + p.drawPixmap(thumb_rect.topLeft(), scaled_thumb); + drawTime(p, thumb_rect, thumbnail_dispaly_time); + } +} - auto it = thumbnails.lowerBound(can->toMonoTime(seconds)); +void StreamCameraView::drawThumbnail(QPainter &p) { + auto it = thumbnails.lowerBound(can->toMonoTime(thumbnail_dispaly_time)); if (it != thumbnails.end()) { const QPixmap &thumb = it.value(); + auto [min_sec, max_sec] = can->timeRange().value_or(std::make_pair(can->minSeconds(), can->maxSeconds())); + int pos = (thumbnail_dispaly_time - min_sec) * width() / (max_sec - min_sec); int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, width() - thumb.width() - THUMBNAIL_MARGIN + 1); int y = height() - thumb.height() - THUMBNAIL_MARGIN; p.drawPixmap(x, y, thumb); - p.setPen(QPen(palette().color(QPalette::BrightText), 2)); - p.setFont(QFont(font().family(), 10)); - p.drawText(x, y, thumb.width(), thumb.height() - THUMBNAIL_MARGIN, - Qt::AlignHCenter | Qt::AlignBottom, QString::number(seconds, 'f', 3)); + drawTime(p, QRect{x, y, thumb.width(), thumb.height()}, thumbnail_dispaly_time); } } +void StreamCameraView::drawTime(QPainter &p, const QRect &rect, double seconds) { + p.setPen(palette().color(QPalette::BrightText)); + p.setFont(QFont(font().family(), 10)); + p.drawText(rect.adjusted(0, 0, 0, -THUMBNAIL_MARGIN), Qt::AlignHCenter | Qt::AlignBottom, QString::number(seconds, 'f', 3)); +} + void StreamCameraView::drawAlert(QPainter &p, const QRect &rect, const Timeline::Entry &alert) { p.setPen(QPen(palette().color(QPalette::BrightText), 2)); QColor color = timeline_colors[int(alert.type)]; @@ -364,14 +402,3 @@ void StreamCameraView::drawAlert(QPainter &p, const QRect &rect, const Timeline: p.fillRect(text_rect.left(), r.top(), text_rect.width(), r.height(), color); p.drawText(text_rect, Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, text); } - -bool StreamCameraView::eventFilter(QObject *, QEvent *event) { - if (event->type() == QEvent::MouseMove) { - thumbnail_pt_ = static_cast(event)->pos(); - update(); - } else if (event->type() == QEvent::Leave) { - thumbnail_pt_ = std::nullopt; - update(); - } - return false; -} diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index d00657b1646f63..1d87a5cfa0fc3b 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -28,6 +27,7 @@ class Slider : public QSlider { void mousePressEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *ev) override; const double factor = 1000.0; + double thumbnail_dispaly_time = -1; }; class StreamCameraView : public CameraWidget { @@ -43,11 +43,14 @@ class StreamCameraView : public CameraWidget { QPixmap generateThumbnail(QPixmap thumbnail, double seconds); void drawAlert(QPainter &p, const QRect &rect, const Timeline::Entry &alert); void drawThumbnail(QPainter &p); - bool eventFilter(QObject *obj, QEvent *event) override; + void drawScrubThumbnail(QPainter &p); + void drawTime(QPainter &p, const QRect &rect, double seconds); QPropertyAnimation *fade_animation; + QMap big_thumbnails; QMap thumbnails; - std::optional thumbnail_pt_; + double thumbnail_dispaly_time = -1; + friend class VideoWidget; }; class VideoWidget : public QFrame { @@ -55,8 +58,10 @@ class VideoWidget : public QFrame { public: VideoWidget(QWidget *parnet = nullptr); + void showThumbnail(double seconds); protected: + bool eventFilter(QObject *obj, QEvent *event) override; QString formatTime(double sec, bool include_milliseconds = false); void timeRangeChanged(); void updateState(); diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 328c12c4181625..2bd3614530abae 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -89,7 +89,7 @@ void Replay::interruptStream(const std::function &update_fn) { interrupt_requested_ = true; std::unique_lock lock(stream_lock_); events_ready_ = update_fn(); - interrupt_requested_ = user_paused_ && !pause_after_next_frame_; + interrupt_requested_ = user_paused_; } stream_cv_.notify_one(); } @@ -116,9 +116,6 @@ void Replay::seekTo(double seconds, bool relative) { seeked_to_sec = *seeking_to_; seeking_to_.reset(); } - - // if paused, resume for exactly one frame to update - pause_after_next_frame_ = user_paused_; return false; }); @@ -147,7 +144,6 @@ void Replay::pause(bool pause) { interruptStream([=]() { rWarning("%s at %.2f s", pause ? "paused..." : "resuming", currentSeconds()); user_paused_ = pause; - pause_after_next_frame_ = false; return !pause; }); } @@ -309,7 +305,6 @@ std::vector::const_iterator Replay::publishEvents(std::vector::con uint64_t evt_start_ts = cur_mono_time_; uint64_t loop_start_ts = nanos_since_boot(); double prev_replay_speed = speed_; - uint64_t first_mono_time = first->mono_time; for (; !interrupt_requested_ && first != last; ++first) { const Event &evt = *first; @@ -348,12 +343,6 @@ std::vector::const_iterator Replay::publishEvents(std::vector::con } publishFrame(&evt); } - - const auto T_ONE_FRAME = 0.050; - if (pause_after_next_frame_ && abs(toSeconds(evt.mono_time) - toSeconds(first_mono_time)) > T_ONE_FRAME) { - pause_after_next_frame_ = false; - interrupt_requested_ = true; - } } return first; diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 6f929cdd192108..d549eaefc4dd76 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -85,7 +85,6 @@ class Replay { std::thread stream_thread_; std::mutex stream_lock_; bool user_paused_ = false; - bool pause_after_next_frame_ = false; std::condition_variable stream_cv_; int current_segment_ = 0; std::optional seeking_to_;