From 1e74bcd9deca412f0beb725cea89107b928272c6 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 26 Sep 2026 21:06:13 +1200 Subject: [PATCH] Cell rulers: no ghost labels on Windows, hidden while the folio's own header shows The Windows 11 style gives QPalette::Button a translucent colour (#FFFFFFB3). The rulers filled their background with it, and since they paint with WA_OpaquePaintEvent nothing clears them first: every zoom step blended the new labels over the old ones, leaving a fading trail. Fill with the button colour composed over the window colour instead, which is always opaque. Each ruler is now also hidden while the folio's own column (or row) header is wholly in sight, so zoomed out only the folio's headers show, not both. When a ruler comes or goes the drawing keeps its place on screen: the ruler covers or uncovers the edge of the viewport. Co-Authored-By: Claude Opus 5.5 --- sources/cellruler.cpp | 23 +++++++++++++++++++++-- sources/cellruler.h | 2 ++ sources/diagramview.cpp | 32 ++++++++++++++++++++++++-------- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/sources/cellruler.cpp b/sources/cellruler.cpp index 299d70eb8..61fa98399 100644 --- a/sources/cellruler.cpp +++ b/sources/cellruler.cpp @@ -73,7 +73,7 @@ void CellRuler::paintEvent(QPaintEvent *event) Q_UNUSED(event) QPainter painter(this); - painter.fillRect(rect(), palette().color(QPalette::Button)); + painter.fillRect(rect(), background()); const bool horizontal = m_orientation == Qt::Horizontal; const int length = horizontal ? width() : height(); @@ -165,6 +165,25 @@ void CellRuler::paintEvent(QPaintEvent *event) if (m_leading_space > 0) { painter.fillRect(horizontal ? QRect(0, 0, m_leading_space, depth - 1) : QRect(0, 0, depth - 1, m_leading_space), - palette().color(QPalette::Button)); + background()); } } + +/** + @brief CellRuler::background + @return the button colour laid over the window colour, always opaque. + The Windows 11 style gives buttons a translucent colour; filled with it + as is, a ruler (painted with Qt::WA_OpaquePaintEvent, so never cleared + first) would let every previous frame show through, and zooming would + leave a shadow of the old labels behind the new ones. +*/ +QColor CellRuler::background() const +{ + const QColor window = palette().color(QPalette::Window); + const QColor button = palette().color(QPalette::Button); + const qreal alpha = button.alphaF(); + return QColor::fromRgbF( + button.redF() * alpha + window.redF() * (1 - alpha), + button.greenF() * alpha + window.greenF() * (1 - alpha), + button.blueF() * alpha + window.blueF() * (1 - alpha)); +} diff --git a/sources/cellruler.h b/sources/cellruler.h index 619b2d52e..586918210 100644 --- a/sources/cellruler.h +++ b/sources/cellruler.h @@ -44,6 +44,8 @@ class CellRuler : public QWidget void paintEvent(QPaintEvent *event) override; private: + QColor background() const; + Qt::Orientation m_orientation; DiagramView *m_view; /// Pixels before the viewport starts, left empty: the corner the diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index 918b6bbbf..b2677d04d 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -1210,17 +1210,24 @@ void DiagramView::setCellRulersShown(bool shown) /** @brief DiagramView::updateCellRulers - Show each ruler when the rulers are wanted and the folio shows the - matching header, and give it room in the margins of the view. The - part of the folio in sight stays in sight when the viewport resizes. + Show each ruler when the rulers are wanted, the folio shows the + matching header and that header is not already wholly in sight, and + give it room in the margins of the view. The drawing does not move on + screen when a ruler comes or goes: the ruler covers or uncovers the + edge of the viewport, as if it lay over it. */ void DiagramView::updateCellRulers() { const BorderTitleBlock &border = m_diagram->border_and_titleblock; + const QRectF in_sight = mapToScene(viewport()->rect()).boundingRect(); + const QRectF columns = border.columnsRect(); + const QRectF rows = border.rowsRect(); const bool top = m_cell_rulers_shown - && border.borderIsDisplayed() && border.columnsAreDisplayed(); + && border.borderIsDisplayed() && border.columnsAreDisplayed() + && (columns.top() < in_sight.top() || columns.bottom() > in_sight.bottom()); const bool side = m_cell_rulers_shown - && border.borderIsDisplayed() && border.rowsAreDisplayed(); + && border.borderIsDisplayed() && border.rowsAreDisplayed() + && (rows.left() < in_sight.left() || rows.right() > in_sight.right()); const int thickness = m_top_ruler->thickness(); m_top_ruler->setVisible(top); @@ -1229,9 +1236,12 @@ void DiagramView::updateCellRulers() const QMargins margins(side ? thickness : 0, top ? thickness : 0, 0, 0); if (margins != viewportMargins()) { - const QPointF centre = mapToScene(viewport()->rect().center()); + const QPointF origin = mapToScene(viewport()->rect().center()); + const QPoint before = viewport()->mapToGlobal(mapFromScene(origin)); setViewportMargins(margins); - centerOn(centre); + const QPoint moved = viewport()->mapToGlobal(mapFromScene(origin)) - before; + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + moved.x()); + verticalScrollBar()->setValue(verticalScrollBar()->value() + moved.y()); } placeCellRulers(); m_top_ruler->update(); @@ -1281,11 +1291,17 @@ void DiagramView::paintEvent(QPaintEvent *event) { PaletteGraphicsView::paintEvent(event); - //Scrolling and zooming both repaint the viewport: follow them + //Scrolling and zooming both repaint the viewport: follow them. + //Showing or hiding a ruler resizes the viewport, which cannot be + //done while it paints. if (viewportTransform() != m_rulers_transform) { m_rulers_transform = viewportTransform(); m_top_ruler->update(); m_side_ruler->update(); + if (m_cell_rulers_shown) { + QMetaObject::invokeMethod(this, &DiagramView::updateCellRulers, + Qt::QueuedConnection); + } } if (m_free_rubberbanding && m_free_rubberband.count() >= 3)