diff --git a/viewer/image_viewer/JFJochAzIntImage.cpp b/viewer/image_viewer/JFJochAzIntImage.cpp index 449ee039..a9905f24 100644 --- a/viewer/image_viewer/JFJochAzIntImage.cpp +++ b/viewer/image_viewer/JFJochAzIntImage.cpp @@ -77,10 +77,9 @@ void JFJochAzIntImage::imageLoaded(std::shared_ptr in_i } } -void JFJochAzIntImage::mouseHover(QMouseEvent* event) { +void JFJochAzIntImage::mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers) { if (!scene() || !image || W == 0 || H == 0) return; - QPointF scenePos = mapToScene(event->pos()); int x = static_cast(scenePos.x()); int y = static_cast(scenePos.y()); diff --git a/viewer/image_viewer/JFJochAzIntImage.h b/viewer/image_viewer/JFJochAzIntImage.h index 08dff6d8..2eb9d3fc 100644 --- a/viewer/image_viewer/JFJochAzIntImage.h +++ b/viewer/image_viewer/JFJochAzIntImage.h @@ -21,7 +21,7 @@ class JFJochAzIntImage : public JFJochImage { float range_max = 1.0f; std::shared_ptr image; - void mouseHover(QMouseEvent* event) override; + void mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers modifiers) override; void Clear(); void mouseDoubleClickEvent(QMouseEvent *event) override; signals: diff --git a/viewer/image_viewer/JFJochDiffractionImage.cpp b/viewer/image_viewer/JFJochDiffractionImage.cpp index 130aa865..1f5ad172 100644 --- a/viewer/image_viewer/JFJochDiffractionImage.cpp +++ b/viewer/image_viewer/JFJochDiffractionImage.cpp @@ -79,8 +79,7 @@ void JFJochDiffractionImage::azimuthalHandles(const ROIAzimuthal &az, const Diff phimax = pt(d_mid, phi1); } -void JFJochDiffractionImage::mouseHover(QMouseEvent *event) { - auto coord = mapToScene(event->pos()); +void JFJochDiffractionImage::mouseHover(const QPointF &coord, Qt::KeyboardModifiers) { if (image && (coord.x() >= 0) && (coord.x() < image->Dataset().experiment.GetXPixelsNum()) diff --git a/viewer/image_viewer/JFJochDiffractionImage.h b/viewer/image_viewer/JFJochDiffractionImage.h index e11e84ae..1d9bc6c3 100644 --- a/viewer/image_viewer/JFJochDiffractionImage.h +++ b/viewer/image_viewer/JFJochDiffractionImage.h @@ -102,7 +102,7 @@ private: float ice_ring_width_Q_recipA = 0.01; - void mouseHover(QMouseEvent* event) override; + void mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers modifiers) override; signals: void roiGeometryEdited(ROIDefinition rois); diff --git a/viewer/image_viewer/JFJochGridScanImage.cpp b/viewer/image_viewer/JFJochGridScanImage.cpp index a98121e6..7bb761fd 100644 --- a/viewer/image_viewer/JFJochGridScanImage.cpp +++ b/viewer/image_viewer/JFJochGridScanImage.cpp @@ -63,13 +63,11 @@ void JFJochGridScanImage::loadData(const std::vector &data, const GridSca CalcROI(); } -void JFJochGridScanImage::mouseHover(QMouseEvent *event) { +void JFJochGridScanImage::mouseHover(const QPointF &pt, Qt::KeyboardModifiers modifiers) { // Map mouse position to image pixel if inside bounds if (W == 0 || H == 0 || image_index.empty()) return; - const QPointF pt = mapToScene(event->pos()); - // Convert view coordinates to image pixel by truncation int x = static_cast(pt.x()); int y = static_cast(pt.y()); @@ -82,7 +80,7 @@ void JFJochGridScanImage::mouseHover(QMouseEvent *event) { return; int64_t image_id = image_index[idx]; if (image_id >= 0) { - if (event->modifiers() & Qt::ShiftModifier) + if (modifiers & Qt::ShiftModifier) emit imageSelected(image_id); if (one_over_d2) { diff --git a/viewer/image_viewer/JFJochGridScanImage.h b/viewer/image_viewer/JFJochGridScanImage.h index 3853a01e..e290a7ef 100644 --- a/viewer/image_viewer/JFJochGridScanImage.h +++ b/viewer/image_viewer/JFJochGridScanImage.h @@ -17,7 +17,7 @@ class JFJochGridScanImage : public JFJochImage { int64_t current_image_H = -1; bool one_over_d2 = false; - void mouseHover(QMouseEvent *event) override; + void mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers modifiers) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void loadImage(QMouseEvent *event); void addCustomOverlay() override; diff --git a/viewer/image_viewer/JFJochImage.cpp b/viewer/image_viewer/JFJochImage.cpp index 22f3f728..ef165b21 100644 --- a/viewer/image_viewer/JFJochImage.cpp +++ b/viewer/image_viewer/JFJochImage.cpp @@ -61,6 +61,10 @@ JFJochImage::JFJochImage(QWidget *parent) : QGraphicsView(parent) { // Optional: a sensible default colormap color_scale.Select(ColorScaleEnum::Indigo); + + hover_tail_timer_ = new QTimer(this); + hover_tail_timer_->setSingleShot(true); + connect(hover_tail_timer_, &QTimer::timeout, this, &JFJochImage::UpdateHover); } void JFJochImage::onScroll(int value) { @@ -69,6 +73,27 @@ void JFJochImage::onScroll(int value) { updateOverlay(); } +void JFJochImage::UpdateHover() { + hover_rate_.restart(); + mouseHover(hover_scene_pos_, hover_modifiers_); + emit hoverScenePos(hover_scene_pos_); +} + +void JFJochImage::ScheduleHoverUpdate(const QPointF &scenePos, Qt::KeyboardModifiers modifiers) { + hover_scene_pos_ = scenePos; + hover_modifiers_ = modifiers; + + if (!hover_rate_.isValid() || hover_rate_.elapsed() >= kHoverIntervalMs) { + hover_tail_timer_->stop(); + UpdateHover(); + return; + } + + // Too soon. Push the catch-up back instead of queueing one per skipped motion, so it fires + // once, after the pointer stops -- a catch-up that fires mid-gesture is an extra repaint. + hover_tail_timer_->start(kHoverIntervalMs); +} + void JFJochImage::ScheduleRenderImage() { if (render_pending_) return; @@ -244,8 +269,7 @@ void JFJochImage::mouseMoveEvent(QMouseEvent *event) { return; const QPointF scenePos = mapToScene(event->pos()); - mouseHover(event); - emit hoverScenePos(scenePos); + ScheduleHoverUpdate(scenePos, event->modifiers()); QPointF delta; switch (mouse_event_type) { diff --git a/viewer/image_viewer/JFJochImage.h b/viewer/image_viewer/JFJochImage.h index e94ccb85..2da38b3d 100644 --- a/viewer/image_viewer/JFJochImage.h +++ b/viewer/image_viewer/JFJochImage.h @@ -5,6 +5,8 @@ #include +#include +#include #include #include #include @@ -151,7 +153,24 @@ protected: QRectF roiBox; static QPointF RoundPoint(const QPointF& p); - virtual void mouseHover(QMouseEvent* event) = 0; + virtual void mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers modifiers) = 0; + + // Hover feedback (status bar, resolution readout, magnifier) used to be regenerated on every + // single mouse motion. Each regeneration dirties the window, and on a remote X session every + // repaint costs a full-window pixel upload, so the thing to minimise is the number of + // repaints. ~15 Hz is far below the motion event rate and well above what the eye follows. + // + // The rate limit is applied inline rather than from a timer on purpose: running the update + // inside the mouse event keeps its damage in the same repaint as anything else that event + // triggers (a pan), instead of costing a second one. The timer only covers the tail, so the + // final position is still reported once the pointer stops. + static constexpr int kHoverIntervalMs = 66; + void ScheduleHoverUpdate(const QPointF &scenePos, Qt::KeyboardModifiers modifiers); + void UpdateHover(); + QPointF hover_scene_pos_; + Qt::KeyboardModifiers hover_modifiers_ = Qt::NoModifier; + QElapsedTimer hover_rate_; + QTimer *hover_tail_timer_ = nullptr; ResizeHandle hitTestROIHandle(const QPointF& scenePos, qreal tol = 3.0) const; diff --git a/viewer/image_viewer/JFJochSimpleImage.cpp b/viewer/image_viewer/JFJochSimpleImage.cpp index 25156a6e..75b43270 100644 --- a/viewer/image_viewer/JFJochSimpleImage.cpp +++ b/viewer/image_viewer/JFJochSimpleImage.cpp @@ -41,9 +41,8 @@ void JFJochSimpleImage::setImage(std::shared_ptr img) { } -void JFJochSimpleImage::mouseHover(QMouseEvent *event) { +void JFJochSimpleImage::mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers) { if (image_) { - const QPointF scenePos = mapToScene(event->pos()); // Hover feedback / status bar display if ((scenePos.x() >= 0) && (scenePos.x() < image_->image.GetWidth()) diff --git a/viewer/image_viewer/JFJochSimpleImage.h b/viewer/image_viewer/JFJochSimpleImage.h index 3232bc7c..92b78697 100644 --- a/viewer/image_viewer/JFJochSimpleImage.h +++ b/viewer/image_viewer/JFJochSimpleImage.h @@ -25,7 +25,7 @@ class JFJochSimpleImage : public JFJochImage { void loadImageInternal(const uint8_t *input); void loadImageInternal(); - void mouseHover(QMouseEvent *event) override; + void mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers modifiers) override; public: explicit JFJochSimpleImage(QWidget *parent = nullptr); public slots: