From 68293395ed6c513703a206389734201d3202c30c Mon Sep 17 00:00:00 2001 From: Jan Arve Saether Date: Tue, 25 Nov 2014 12:15:37 +0100 Subject: Add support for snapping to pixel grid This enables us to do more intelligent distribution than simply doing the rounding on each individual items geometry (which often leads to larger spacings than specified). Instead of doing the rounding on the output geometries, we now do the snapping inside the layout engine. This allows us to do more intelligent distribution of items, and spacings should always be respected. There are some cases where items with fractional size hints might overlap with less than a pixel. This was also the case before this patch. Those cases are impossible to fix properly, since fractional size hints conflicts with the snapping in some cases. (Fractional size hints is normal for Text items.) Task-number: QTBUG-41216 Change-Id: I01a8bc3529f0b8b028d6eb0a530c751b67ac6f4e Reviewed-by: Paul Olav Tvete --- src/gui/util/qgridlayoutengine.cpp | 59 ++++++++++++++++++++++++++------------ src/gui/util/qgridlayoutengine_p.h | 9 ++++-- 2 files changed, 47 insertions(+), 21 deletions(-) (limited to 'src/gui/util') diff --git a/src/gui/util/qgridlayoutengine.cpp b/src/gui/util/qgridlayoutengine.cpp index 861b713efd..572dce9675 100644 --- a/src/gui/util/qgridlayoutengine.cpp +++ b/src/gui/util/qgridlayoutengine.cpp @@ -154,7 +154,7 @@ void QGridLayoutRowData::reset(int count) hasIgnoreFlag = false; } -void QGridLayoutRowData::distributeMultiCells(const QGridLayoutRowInfo &rowInfo) +void QGridLayoutRowData::distributeMultiCells(const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid) { MultiCellMap::const_iterator i = multiCellMap.constBegin(); for (; i != multiCellMap.constEnd(); ++i) { @@ -173,7 +173,7 @@ void QGridLayoutRowData::distributeMultiCells(const QGridLayoutRowInfo &rowInfo) qreal extra = compare(box, totalBox, j); if (extra > 0.0) { calculateGeometries(start, end, box.q_sizes(j), dummy.data(), newSizes.data(), - 0, totalBox, rowInfo); + 0, totalBox, rowInfo, snapToPixelGrid); for (int k = 0; k < span; ++k) extras[k].q_sizes(j) = newSizes[k]; @@ -188,11 +188,19 @@ void QGridLayoutRowData::distributeMultiCells(const QGridLayoutRowInfo &rowInfo) } multiCellMap.clear(); } +namespace { +// does not return int +static inline qreal qround(qreal f) +{ + return floor(f + 0.5); +} + +} void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSize, qreal *positions, qreal *sizes, qreal *descents, const QGridLayoutBox &totalBox, - const QGridLayoutRowInfo &rowInfo) + const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid) { Q_ASSERT(end > start); @@ -335,17 +343,19 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz bool keepGoing = somethingHasAMaximumSize; while (keepGoing) { + //sumCurrentAvailable is so large that something *might* reach its maximum size keepGoing = false; for (int i = 0; i < n; ++i) { if (newSizes[i] >= 0.0) continue; - qreal maxBoxSize; - if (isLargerThanMaximum) - maxBoxSize = rowInfo.boxes.value(start + i).q_maximumSize; - else - maxBoxSize = boxes.at(start + i).q_maximumSize; + const QVector &rBoxes = isLargerThanMaximum ? rowInfo.boxes : boxes; + const QGridLayoutBox &box = rBoxes.value(start + i); + qreal maxBoxSize = box.q_maximumSize; + + if (snapToPixelGrid) + maxBoxSize = qMax(box.q_minimumSize, floor(maxBoxSize)); qreal avail = sumCurrentAvailable * factors[i] / sumFactors; if (sizes[i] + avail >= maxBoxSize) { @@ -358,7 +368,6 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz } } } - for (int i = 0; i < n; ++i) { if (newSizes[i] < 0.0) { qreal delta = (sumFactors == 0.0) ? 0.0 @@ -402,6 +411,10 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz Q_ASSERT(surplus == 0); #endif } + if (snapToPixelGrid) { + for (int i = 0; i < n; ++i) + positions[i] = qround(positions[i]); + } if (descents) { for (int i = 0; i < n; ++i) { @@ -753,10 +766,11 @@ void QGridLayoutRowInfo::dump(int indent) const } #endif -QGridLayoutEngine::QGridLayoutEngine(Qt::Alignment defaultAlignment) +QGridLayoutEngine::QGridLayoutEngine(Qt::Alignment defaultAlignment, bool snapToPixelGrid) { m_visualDirection = Qt::LeftToRight; m_defaultAlignment = defaultAlignment; + m_snapToPixelGrid = snapToPixelGrid; invalidate(); } @@ -1007,8 +1021,17 @@ void QGridLayoutEngine::setGeometries(const QRectF &contentsGeometry, const QAbs if (item->rowSpan() != 1) height += q_yy[item->lastRow()] - y; + const Qt::Alignment align = effectiveAlignment(item); QRectF geom = item->geometryWithin(contentsGeometry.x() + x, contentsGeometry.y() + y, - width, height, q_descents[item->lastRow()], effectiveAlignment(item)); + width, height, q_descents[item->lastRow()], align); + if (m_snapToPixelGrid) { + // x and y should already be rounded, but the call to geometryWithin() above might + // result in a geom with x,y at half-pixels (due to centering within the cell) + geom.setX(qround(geom.x())); + // Do not snap baseline aligned items, since that might cause the baselines to not be aligned. + if (align != Qt::AlignBaseline) + geom.setY(qround(geom.y())); + } visualRect(&geom, visualDirection(), contentsGeometry); item->setGeometry(geom); } @@ -1061,7 +1084,7 @@ QSizeF QGridLayoutEngine::sizeHint(Qt::SizeHint which, const QSizeF &constraint, //Calculate column widths and positions, and put results in q_xx.data() and q_widths.data() so that we can use this information as //constraints to find the row heights q_columnData.calculateGeometries(0, columnCount(), width, sizehint_xx.data(), sizehint_widths.data(), - 0, sizehint_totalBoxes[Hor], q_infos[Hor]); + 0, sizehint_totalBoxes[Hor], q_infos[Hor], m_snapToPixelGrid); ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Ver], sizehint_xx.data(), sizehint_widths.data(), Qt::Vertical, styleInfo); sizeHintCalculated = true; } @@ -1078,7 +1101,7 @@ QSizeF QGridLayoutEngine::sizeHint(Qt::SizeHint which, const QSizeF &constraint, //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() so that we can use this information as //constraints to find the column widths q_rowData.calculateGeometries(0, rowCount(), height, sizehint_yy.data(), sizehint_heights.data(), - 0, sizehint_totalBoxes[Ver], q_infos[Ver]); + 0, sizehint_totalBoxes[Ver], q_infos[Ver], m_snapToPixelGrid); ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Hor], sizehint_yy.data(), sizehint_heights.data(), Qt::Horizontal, styleInfo); sizeHintCalculated = true; } @@ -1533,7 +1556,7 @@ void QGridLayoutEngine::ensureColumnAndRowData(QGridLayoutRowData *rowData, QGri rowData->reset(rowCount(orientation)); fillRowData(rowData, colPositions, colSizes, orientation, styleInfo); const QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; - rowData->distributeMultiCells(rowInfo); + rowData->distributeMultiCells(rowInfo, m_snapToPixelGrid); *totalBox = rowData->totalBox(0, rowCount(orientation)); if (totalBox != &q_totalBoxes[o]) @@ -1605,22 +1628,22 @@ void QGridLayoutEngine::ensureGeometries(const QSizeF &size, //Calculate column widths and positions, and put results in q_xx.data() and q_widths.data() so that we can use this information as //constraints to find the row heights q_columnData.calculateGeometries(0, columnCount(), size.width(), q_xx.data(), q_widths.data(), - 0, q_totalBoxes[Hor], q_infos[Hor] ); + 0, q_totalBoxes[Hor], q_infos[Hor], m_snapToPixelGrid); ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Ver], q_xx.data(), q_widths.data(), Qt::Vertical, styleInfo); //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() q_rowData.calculateGeometries(0, rowCount(), size.height(), q_yy.data(), q_heights.data(), - q_descents.data(), q_totalBoxes[Ver], q_infos[Ver]); + q_descents.data(), q_totalBoxes[Ver], q_infos[Ver], m_snapToPixelGrid); } else { //We have items whose width depends on their height (WFH) ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Ver], NULL, NULL, Qt::Vertical, styleInfo); //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() so that we can use this information as //constraints to find the column widths q_rowData.calculateGeometries(0, rowCount(), size.height(), q_yy.data(), q_heights.data(), - q_descents.data(), q_totalBoxes[Ver], q_infos[Ver]); + q_descents.data(), q_totalBoxes[Ver], q_infos[Ver], m_snapToPixelGrid); ensureColumnAndRowData(&q_columnData, &q_totalBoxes[Hor], q_yy.data(), q_heights.data(), Qt::Horizontal, styleInfo); //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() q_columnData.calculateGeometries(0, columnCount(), size.width(), q_xx.data(), q_widths.data(), - 0, q_totalBoxes[Hor], q_infos[Hor]); + 0, q_totalBoxes[Hor], q_infos[Hor], m_snapToPixelGrid); } } diff --git a/src/gui/util/qgridlayoutengine_p.h b/src/gui/util/qgridlayoutengine_p.h index b75312bfe8..1abeefe56a 100644 --- a/src/gui/util/qgridlayoutengine_p.h +++ b/src/gui/util/qgridlayoutengine_p.h @@ -226,10 +226,10 @@ class QGridLayoutRowData { public: void reset(int count); - void distributeMultiCells(const QGridLayoutRowInfo &rowInfo); + void distributeMultiCells(const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid); void calculateGeometries(int start, int end, qreal targetSize, qreal *positions, qreal *sizes, qreal *descents, const QGridLayoutBox &totalBox, - const QGridLayoutRowInfo &rowInfo); + const QGridLayoutRowInfo &rowInfo, bool snapToPixelGrid); QGridLayoutBox totalBox(int start, int end) const; void stealBox(int start, int end, int which, qreal *positions, qreal *sizes); @@ -331,7 +331,7 @@ private: class Q_GUI_EXPORT QGridLayoutEngine { public: - QGridLayoutEngine(Qt::Alignment defaultAlignment = Qt::Alignment(0)); + QGridLayoutEngine(Qt::Alignment defaultAlignment = Qt::Alignment(0), bool snapToPixelGrid = false); inline ~QGridLayoutEngine() { qDeleteAll(q_items); } int rowCount(Qt::Orientation orientation) const; @@ -436,7 +436,10 @@ private: QLayoutParameter q_defaultSpacings[NOrientations]; QGridLayoutRowInfo q_infos[NOrientations]; Qt::LayoutDirection m_visualDirection; + + // Configuration Qt::Alignment m_defaultAlignment; + unsigned m_snapToPixelGrid : 1; // Lazily computed from the above user input mutable int q_cachedEffectiveFirstRows[NOrientations]; -- cgit v1.2.3