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 <paul.tvete@theqtcompany.com>
This commit is contained in:
Jan Arve Saether 2014-11-25 12:15:37 +01:00 committed by Jan Arve Sæther
parent 18aae36a90
commit 68293395ed
2 changed files with 47 additions and 21 deletions

View File

@ -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<QGridLayoutBox> &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);
}
}

View File

@ -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<qreal> 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];