viewer: coalesce overlay rebuilds from navigation events

updateOverlay() tears down and recreates every overlay item (spots,
predictions, hkl labels, rings, ROIs). It was called directly from every
scroll, wheel, pan and resize event. A single pan step emits valueChanged
on both scrollbars (each -> onScroll -> updateOverlay) plus a direct call,
so one visual change triggered ~3 full overlay rebuilds.

Route the navigation hot paths through scheduleOverlayUpdate(), which sets
a dirty flag and defers the rebuild to the next event-loop pass via
QTimer::singleShot(0). Bursts within one handler collapse to a single
rebuild. The synchronous one-offs (image load, ROI edit, viewport-lock
apply, setZoom) keep calling updateOverlay() directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 11:02:35 +02:00
co-authored by Claude Opus 4.8
parent 975ae421f0
commit 54b1699d1f
2 changed files with 26 additions and 5 deletions
+21 -5
View File
@@ -43,7 +43,7 @@ JFJochImage::JFJochImage(QWidget *parent) : QGraphicsView(parent) {
}
void JFJochImage::onScroll(int value) {
updateOverlay();
scheduleOverlayUpdate();
}
void JFJochImage::changeBackground(float val) {
@@ -122,7 +122,7 @@ void JFJochImage::wheelEvent(QWheelEvent *event) {
QPointF delta = targetScenePos - updatedViewportCenter;
translate(delta.x(), delta.y()); // Shift the view
updateOverlay();
scheduleOverlayUpdate();
emitViewportChanged();
}
}
@@ -140,7 +140,7 @@ void JFJochImage::resizeEvent(QResizeEvent *event) {
if (!initial_fit_done_)
fitToViewShorterSideOnce();
updateOverlay();
scheduleOverlayUpdate();
}
QPointF JFJochImage::RoundPoint(const QPointF &input) {
@@ -221,7 +221,7 @@ void JFJochImage::mouseMoveEvent(QMouseEvent *event) {
horizontalScrollBar()->setValue(horizontalScrollBar()->value() - viewDelta.x());
verticalScrollBar()->setValue(verticalScrollBar()->value() - viewDelta.y());
updateOverlay();
scheduleOverlayUpdate();
emitViewportChanged();
break;
}
@@ -272,7 +272,7 @@ void JFJochImage::mouseMoveEvent(QMouseEvent *event) {
// Update hover state so overlay can draw arrows/handles accordingly
if (h != hover_handle_) {
hover_handle_ = h;
updateOverlay();
scheduleOverlayUpdate();
}
// Set an informative cursor
switch (h) {
@@ -837,6 +837,22 @@ void JFJochImage::resetScenePointers() {
overlay_items_.clear();
}
void JFJochImage::scheduleOverlayUpdate() {
// Coalesce a burst of navigation events into one overlay rebuild. A single
// pan step, for instance, emits valueChanged on both scrollbars plus a
// direct call - 3 rebuilds for one visual change. Rebuilding tears down and
// recreates every spot/prediction/ROI item, so collapsing the duplicates to
// one rebuild on the next event-loop pass sharply cuts CPU and, over remote
// X forwarding, rasterised-pixel traffic.
if (overlay_update_scheduled_)
return;
overlay_update_scheduled_ = true;
QTimer::singleShot(0, this, [this]() {
overlay_update_scheduled_ = false;
updateOverlay();
});
}
void JFJochImage::updateOverlay() {
if (!scene() || W * H <= 0) return;
+5
View File
@@ -17,6 +17,10 @@ class JFJochImage : public QGraphicsView {
bool m_adjustForegroundWithWheel = false;
// Coalescing guard for scheduleOverlayUpdate(): collapses a burst of
// navigation events into a single overlay rebuild on the next event loop.
bool overlay_update_scheduled_ = false;
// Viewport-lock support: guard prevents the emit<->apply ping-pong between
// two linked views, and the helper broadcasts the current transform+center.
bool m_applyingViewport = false;
@@ -104,6 +108,7 @@ protected:
ResizeHandle hitTestROIHandle(const QPointF& scenePos, qreal tol = 3.0) const;
void updateOverlay();
void scheduleOverlayUpdate();
void GeneratePixmap();
void Redraw();
void CalcROI();