File indexing completed on 2024-11-10 04:57:05

0001 /*
0002     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     // The layouting code is taken from the present windows effect.
0005     SPDX-FileCopyrightText: 2007 Rivo Laks <rivolaks@hot.ee>
0006     SPDX-FileCopyrightText: 2008 Lucas Murray <lmurray@undefinedfire.com>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "expolayout.h"
0012 
0013 #include <cmath>
0014 
0015 ExpoCell::ExpoCell(QObject *parent)
0016     : QObject(parent)
0017 {
0018 }
0019 
0020 ExpoCell::~ExpoCell()
0021 {
0022     setLayout(nullptr);
0023 }
0024 
0025 ExpoLayout *ExpoCell::layout() const
0026 {
0027     return m_layout;
0028 }
0029 
0030 void ExpoCell::setLayout(ExpoLayout *layout)
0031 {
0032     if (m_layout == layout) {
0033         return;
0034     }
0035     if (m_layout) {
0036         m_layout->removeCell(this);
0037     }
0038     m_layout = layout;
0039     if (m_layout && m_enabled) {
0040         m_layout->addCell(this);
0041     }
0042     Q_EMIT layoutChanged();
0043 }
0044 
0045 bool ExpoCell::isEnabled() const
0046 {
0047     return m_enabled;
0048 }
0049 
0050 void ExpoCell::setEnabled(bool enabled)
0051 {
0052     if (m_enabled != enabled) {
0053         m_enabled = enabled;
0054         if (enabled) {
0055             if (m_layout) {
0056                 m_layout->addCell(this);
0057             }
0058         } else {
0059             if (m_layout) {
0060                 m_layout->removeCell(this);
0061             }
0062         }
0063         Q_EMIT enabledChanged();
0064     }
0065 }
0066 
0067 void ExpoCell::update()
0068 {
0069     if (m_layout) {
0070         m_layout->polish();
0071     }
0072 }
0073 
0074 int ExpoCell::naturalX() const
0075 {
0076     return m_naturalX;
0077 }
0078 
0079 void ExpoCell::setNaturalX(int x)
0080 {
0081     if (m_naturalX != x) {
0082         m_naturalX = x;
0083         update();
0084         Q_EMIT naturalXChanged();
0085     }
0086 }
0087 
0088 int ExpoCell::naturalY() const
0089 {
0090     return m_naturalY;
0091 }
0092 
0093 void ExpoCell::setNaturalY(int y)
0094 {
0095     if (m_naturalY != y) {
0096         m_naturalY = y;
0097         update();
0098         Q_EMIT naturalYChanged();
0099     }
0100 }
0101 
0102 int ExpoCell::naturalWidth() const
0103 {
0104     return m_naturalWidth;
0105 }
0106 
0107 void ExpoCell::setNaturalWidth(int width)
0108 {
0109     if (m_naturalWidth != width) {
0110         m_naturalWidth = width;
0111         update();
0112         Q_EMIT naturalWidthChanged();
0113     }
0114 }
0115 
0116 int ExpoCell::naturalHeight() const
0117 {
0118     return m_naturalHeight;
0119 }
0120 
0121 void ExpoCell::setNaturalHeight(int height)
0122 {
0123     if (m_naturalHeight != height) {
0124         m_naturalHeight = height;
0125         update();
0126         Q_EMIT naturalHeightChanged();
0127     }
0128 }
0129 
0130 QRect ExpoCell::naturalRect() const
0131 {
0132     return QRect(naturalX(), naturalY(), naturalWidth(), naturalHeight());
0133 }
0134 
0135 QMargins ExpoCell::margins() const
0136 {
0137     return m_margins;
0138 }
0139 
0140 int ExpoCell::x() const
0141 {
0142     return m_x.value_or(0);
0143 }
0144 
0145 void ExpoCell::setX(int x)
0146 {
0147     if (m_x != x) {
0148         m_x = x;
0149         Q_EMIT xChanged();
0150     }
0151 }
0152 
0153 int ExpoCell::y() const
0154 {
0155     return m_y.value_or(0);
0156 }
0157 
0158 void ExpoCell::setY(int y)
0159 {
0160     if (m_y != y) {
0161         m_y = y;
0162         Q_EMIT yChanged();
0163     }
0164 }
0165 
0166 int ExpoCell::width() const
0167 {
0168     return m_width.value_or(0);
0169 }
0170 
0171 void ExpoCell::setWidth(int width)
0172 {
0173     if (m_width != width) {
0174         m_width = width;
0175         Q_EMIT widthChanged();
0176     }
0177 }
0178 
0179 int ExpoCell::height() const
0180 {
0181     return m_height.value_or(0);
0182 }
0183 
0184 void ExpoCell::setHeight(int height)
0185 {
0186     if (m_height != height) {
0187         m_height = height;
0188         Q_EMIT heightChanged();
0189     }
0190 }
0191 
0192 QString ExpoCell::persistentKey() const
0193 {
0194     return m_persistentKey;
0195 }
0196 
0197 void ExpoCell::setPersistentKey(const QString &key)
0198 {
0199     if (m_persistentKey != key) {
0200         m_persistentKey = key;
0201         update();
0202         Q_EMIT persistentKeyChanged();
0203     }
0204 }
0205 
0206 int ExpoCell::bottomMargin() const
0207 {
0208     return m_margins.bottom();
0209 }
0210 
0211 void ExpoCell::setBottomMargin(int margin)
0212 {
0213     if (m_margins.bottom() != margin) {
0214         m_margins.setBottom(margin);
0215         update();
0216         Q_EMIT bottomMarginChanged();
0217     }
0218 }
0219 
0220 ExpoLayout::ExpoLayout(QQuickItem *parent)
0221     : QQuickItem(parent)
0222 {
0223 }
0224 
0225 ExpoLayout::LayoutMode ExpoLayout::mode() const
0226 {
0227     return m_mode;
0228 }
0229 
0230 void ExpoLayout::setMode(LayoutMode mode)
0231 {
0232     if (m_mode != mode) {
0233         m_mode = mode;
0234         polish();
0235         Q_EMIT modeChanged();
0236     }
0237 }
0238 
0239 bool ExpoLayout::fillGaps() const
0240 {
0241     return m_fillGaps;
0242 }
0243 
0244 void ExpoLayout::setFillGaps(bool fill)
0245 {
0246     if (m_fillGaps != fill) {
0247         m_fillGaps = fill;
0248         polish();
0249         Q_EMIT fillGapsChanged();
0250     }
0251 }
0252 
0253 int ExpoLayout::spacing() const
0254 {
0255     return m_spacing;
0256 }
0257 
0258 void ExpoLayout::setSpacing(int spacing)
0259 {
0260     if (m_spacing != spacing) {
0261         m_spacing = spacing;
0262         polish();
0263         Q_EMIT spacingChanged();
0264     }
0265 }
0266 
0267 bool ExpoLayout::isReady() const
0268 {
0269     return m_ready;
0270 }
0271 
0272 void ExpoLayout::setReady()
0273 {
0274     if (!m_ready) {
0275         m_ready = true;
0276         Q_EMIT readyChanged();
0277     }
0278 }
0279 
0280 void ExpoLayout::forceLayout()
0281 {
0282     updatePolish();
0283 }
0284 
0285 void ExpoLayout::updatePolish()
0286 {
0287     if (!m_cells.isEmpty()) {
0288         switch (m_mode) {
0289         case LayoutClosest:
0290             calculateWindowTransformationsClosest();
0291             break;
0292         case LayoutNatural:
0293             calculateWindowTransformationsNatural();
0294             break;
0295         case LayoutNone:
0296             resetTransformations();
0297             break;
0298         }
0299     }
0300 
0301     setReady();
0302 }
0303 
0304 void ExpoLayout::addCell(ExpoCell *cell)
0305 {
0306     Q_ASSERT(!m_cells.contains(cell));
0307     m_cells.append(cell);
0308     polish();
0309 }
0310 
0311 void ExpoLayout::removeCell(ExpoCell *cell)
0312 {
0313     m_cells.removeOne(cell);
0314     polish();
0315 }
0316 
0317 void ExpoLayout::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
0318 {
0319     if (newGeometry.size() != oldGeometry.size()) {
0320         polish();
0321     }
0322     QQuickItem::geometryChange(newGeometry, oldGeometry);
0323 }
0324 
0325 static int distance(const QPoint &a, const QPoint &b)
0326 {
0327     const int xdiff = a.x() - b.x();
0328     const int ydiff = a.y() - b.y();
0329     return int(std::sqrt(qreal(xdiff * xdiff + ydiff * ydiff)));
0330 }
0331 
0332 static QRect centered(ExpoCell *cell, const QRect &bounds)
0333 {
0334     const QSize scaled = QSize(cell->naturalWidth(), cell->naturalHeight())
0335                              .scaled(bounds.size(), Qt::KeepAspectRatio);
0336 
0337     return QRect(bounds.center().x() - scaled.width() / 2,
0338                  bounds.center().y() - scaled.height() / 2,
0339                  scaled.width(),
0340                  scaled.height());
0341 }
0342 
0343 void ExpoLayout::calculateWindowTransformationsClosest()
0344 {
0345     QRect area = QRect(0, 0, width(), height());
0346     const int columns = int(std::ceil(std::sqrt(qreal(m_cells.count()))));
0347     const int rows = int(std::ceil(m_cells.count() / qreal(columns)));
0348 
0349     // Assign slots
0350     const int slotWidth = area.width() / columns;
0351     const int slotHeight = area.height() / rows;
0352     QList<ExpoCell *> takenSlots;
0353     takenSlots.resize(rows * columns);
0354     takenSlots.fill(nullptr);
0355 
0356     // precalculate all slot centers
0357     QList<QPoint> slotCenters;
0358     slotCenters.resize(rows * columns);
0359     for (int x = 0; x < columns; ++x) {
0360         for (int y = 0; y < rows; ++y) {
0361             slotCenters[x + y * columns] = QPoint(area.x() + slotWidth * x + slotWidth / 2,
0362                                                   area.y() + slotHeight * y + slotHeight / 2);
0363         }
0364     }
0365 
0366     // Assign each window to the closest available slot
0367     QList<ExpoCell *> tmpList = m_cells; // use a QLinkedList copy instead?
0368     while (!tmpList.isEmpty()) {
0369         ExpoCell *cell = tmpList.first();
0370         int slotCandidate = -1, slotCandidateDistance = INT_MAX;
0371         const QPoint pos = cell->naturalRect().center();
0372 
0373         for (int i = 0; i < columns * rows; ++i) { // all slots
0374             const int dist = distance(pos, slotCenters[i]);
0375             if (dist < slotCandidateDistance) { // window is interested in this slot
0376                 ExpoCell *occupier = takenSlots[i];
0377                 Q_ASSERT(occupier != cell);
0378                 if (!occupier || dist < distance(occupier->naturalRect().center(), slotCenters[i])) {
0379                     // either nobody lives here, or we're better - takeover the slot if it's our best
0380                     slotCandidate = i;
0381                     slotCandidateDistance = dist;
0382                 }
0383             }
0384         }
0385         Q_ASSERT(slotCandidate != -1);
0386         if (takenSlots[slotCandidate]) {
0387             tmpList << takenSlots[slotCandidate]; // occupier needs a new home now :p
0388         }
0389         tmpList.removeAll(cell);
0390         takenSlots[slotCandidate] = cell; // ...and we rumble in =)
0391     }
0392 
0393     for (int slot = 0; slot < columns * rows; ++slot) {
0394         ExpoCell *cell = takenSlots[slot];
0395         if (!cell) { // some slots might be empty
0396             continue;
0397         }
0398 
0399         // Work out where the slot is
0400         QRect target(area.x() + (slot % columns) * slotWidth,
0401                      area.y() + (slot / columns) * slotHeight,
0402                      slotWidth, slotHeight);
0403         QRect adjustedTarget = target.adjusted(m_spacing, m_spacing, -m_spacing, -m_spacing);
0404         if (adjustedTarget.isValid()) {
0405             target = adjustedTarget; // Borders
0406         }
0407         target = target.marginsRemoved(cell->margins());
0408 
0409         qreal scale;
0410         if (target.width() / qreal(cell->naturalWidth()) < target.height() / qreal(cell->naturalHeight())) {
0411             // Center vertically
0412             scale = target.width() / qreal(cell->naturalWidth());
0413             target.moveTop(target.top() + (target.height() - int(cell->naturalHeight() * scale)) / 2);
0414             target.setHeight(int(cell->naturalHeight() * scale));
0415         } else {
0416             // Center horizontally
0417             scale = target.height() / qreal(cell->naturalHeight());
0418             target.moveLeft(target.left() + (target.width() - int(cell->naturalWidth() * scale)) / 2);
0419             target.setWidth(int(cell->naturalWidth() * scale));
0420         }
0421         // Don't scale the windows too much
0422         if (scale > 2.0 || (scale > 1.0 && (cell->naturalWidth() > 300 || cell->naturalHeight() > 300))) {
0423             scale = (cell->naturalWidth() > 300 || cell->naturalHeight() > 300) ? 1.0 : 2.0;
0424             target = QRect(
0425                 target.center().x() - int(cell->naturalWidth() * scale) / 2,
0426                 target.center().y() - int(cell->naturalHeight() * scale) / 2,
0427                 scale * cell->naturalWidth(), scale * cell->naturalHeight());
0428         }
0429 
0430         cell->setX(target.x());
0431         cell->setY(target.y());
0432         cell->setWidth(target.width());
0433         cell->setHeight(target.height());
0434     }
0435 }
0436 
0437 static inline int heightForWidth(ExpoCell *cell, int width)
0438 {
0439     return int((width / qreal(cell->naturalWidth())) * cell->naturalHeight());
0440 }
0441 
0442 static bool isOverlappingAny(ExpoCell *w, const QHash<ExpoCell *, QRect> &targets, const QRegion &border, int spacing)
0443 {
0444     QHash<ExpoCell *, QRect>::const_iterator winTarget = targets.find(w);
0445     if (winTarget == targets.constEnd()) {
0446         return false;
0447     }
0448     if (border.intersects(*winTarget)) {
0449         return true;
0450     }
0451     const QMargins halfSpacing(spacing / 2, spacing / 2, spacing / 2, spacing / 2);
0452 
0453     // Is there a better way to do this?
0454     QHash<ExpoCell *, QRect>::const_iterator target;
0455     for (target = targets.constBegin(); target != targets.constEnd(); ++target) {
0456         if (target == winTarget) {
0457             continue;
0458         }
0459         if (winTarget->marginsAdded(halfSpacing).intersects(target->marginsAdded(halfSpacing))) {
0460             return true;
0461         }
0462     }
0463     return false;
0464 }
0465 
0466 void ExpoLayout::calculateWindowTransformationsNatural()
0467 {
0468     const QRect area = QRect(0, 0, width(), height());
0469 
0470     // As we are using pseudo-random movement (See "slot") we need to make sure the list
0471     // is always sorted the same way no matter which window is currently active.
0472     std::sort(m_cells.begin(), m_cells.end(), [](const ExpoCell *a, const ExpoCell *b) {
0473         return a->persistentKey() < b->persistentKey();
0474     });
0475 
0476     QRect bounds;
0477     int direction = 0;
0478     QHash<ExpoCell *, QRect> targets;
0479     QHash<ExpoCell *, int> directions;
0480 
0481     for (ExpoCell *cell : std::as_const(m_cells)) {
0482         const QRect cellRect(cell->naturalX(), cell->naturalY(), cell->naturalWidth(), cell->naturalHeight());
0483         targets[cell] = cellRect;
0484         // Reuse the unused "slot" as a preferred direction attribute. This is used when the window
0485         // is on the edge of the screen to try to use as much screen real estate as possible.
0486         directions[cell] = direction;
0487         bounds = bounds.united(cellRect);
0488         direction++;
0489         if (direction == 4) {
0490             direction = 0;
0491         }
0492     }
0493 
0494     // Iterate over all windows, if two overlap push them apart _slightly_ as we try to
0495     // brute-force the most optimal positions over many iterations.
0496     const int halfSpacing = m_spacing / 2;
0497     bool overlap;
0498     do {
0499         overlap = false;
0500         for (ExpoCell *cell : std::as_const(m_cells)) {
0501             QRect *target_w = &targets[cell];
0502             for (ExpoCell *e : std::as_const(m_cells)) {
0503                 if (cell == e) {
0504                     continue;
0505                 }
0506 
0507                 QRect *target_e = &targets[e];
0508                 if (target_w->adjusted(-halfSpacing, -halfSpacing, halfSpacing, halfSpacing)
0509                         .intersects(target_e->adjusted(-halfSpacing, -halfSpacing, halfSpacing, halfSpacing))) {
0510                     overlap = true;
0511 
0512                     // Determine pushing direction
0513                     QPoint diff(target_e->center() - target_w->center());
0514                     // Prevent dividing by zero and non-movement
0515                     if (diff.x() == 0 && diff.y() == 0) {
0516                         diff.setX(1);
0517                     }
0518                     // Try to keep screen aspect ratio
0519                     // if (bounds.height() / bounds.width() > area.height() / area.width())
0520                     //    diff.setY(diff.y() / 2);
0521                     // else
0522                     //    diff.setX(diff.x() / 2);
0523                     // Approximate a vector of between 10px and 20px in magnitude in the same direction
0524                     diff *= m_accuracy / qreal(diff.manhattanLength());
0525                     // Move both windows apart
0526                     target_w->translate(-diff);
0527                     target_e->translate(diff);
0528 
0529                     // Try to keep the bounding rect the same aspect as the screen so that more
0530                     // screen real estate is utilised. We do this by splitting the screen into nine
0531                     // equal sections, if the window center is in any of the corner sections pull the
0532                     // window towards the outer corner. If it is in any of the other edge sections
0533                     // alternate between each corner on that edge. We don't want to determine it
0534                     // randomly as it will not produce consistant locations when using the filter.
0535                     // Only move one window so we don't cause large amounts of unnecessary zooming
0536                     // in some situations. We need to do this even when expanding later just in case
0537                     // all windows are the same size.
0538                     // (We are using an old bounding rect for this, hopefully it doesn't matter)
0539                     int xSection = (target_w->x() - bounds.x()) / (bounds.width() / 3);
0540                     int ySection = (target_w->y() - bounds.y()) / (bounds.height() / 3);
0541                     diff = QPoint(0, 0);
0542                     if (xSection != 1 || ySection != 1) { // Remove this if you want the center to pull as well
0543                         if (xSection == 1) {
0544                             xSection = (directions[cell] / 2 ? 2 : 0);
0545                         }
0546                         if (ySection == 1) {
0547                             ySection = (directions[cell] % 2 ? 2 : 0);
0548                         }
0549                     }
0550                     if (xSection == 0 && ySection == 0) {
0551                         diff = QPoint(bounds.topLeft() - target_w->center());
0552                     }
0553                     if (xSection == 2 && ySection == 0) {
0554                         diff = QPoint(bounds.topRight() - target_w->center());
0555                     }
0556                     if (xSection == 2 && ySection == 2) {
0557                         diff = QPoint(bounds.bottomRight() - target_w->center());
0558                     }
0559                     if (xSection == 0 && ySection == 2) {
0560                         diff = QPoint(bounds.bottomLeft() - target_w->center());
0561                     }
0562                     if (diff.x() != 0 || diff.y() != 0) {
0563                         diff *= m_accuracy / qreal(diff.manhattanLength());
0564                         target_w->translate(diff);
0565                     }
0566 
0567                     // Update bounding rect
0568                     bounds = bounds.united(*target_w);
0569                     bounds = bounds.united(*target_e);
0570                 }
0571             }
0572         }
0573     } while (overlap);
0574 
0575     // Compute the scale factor so the bounding rect fits the target area.
0576     qreal scale;
0577     if (bounds.width() <= area.width() && bounds.height() <= area.height()) {
0578         scale = 1.0;
0579     } else if (area.width() / qreal(bounds.width()) < area.height() / qreal(bounds.height())) {
0580         scale = area.width() / qreal(bounds.width());
0581     } else {
0582         scale = area.height() / qreal(bounds.height());
0583     }
0584     // Make bounding rect fill the screen size for later steps
0585     bounds = QRect(bounds.x() - (area.width() / scale - bounds.width()) / 2,
0586                    bounds.y() - (area.height() / scale - bounds.height()) / 2,
0587                    area.width() / scale,
0588                    area.height() / scale);
0589 
0590     // Move all windows back onto the screen and set their scale
0591     QHash<ExpoCell *, QRect>::iterator target = targets.begin();
0592     while (target != targets.end()) {
0593         target->setRect((target->x() - bounds.x()) * scale + area.x(),
0594                         (target->y() - bounds.y()) * scale + area.y(),
0595                         target->width() * scale,
0596                         target->height() * scale);
0597         ++target;
0598     }
0599 
0600     // Try to fill the gaps by enlarging windows if they have the space
0601     if (m_fillGaps) {
0602         // Don't expand onto or over the border
0603         QRegion borderRegion(area.adjusted(-200, -200, 200, 200));
0604         borderRegion ^= area;
0605 
0606         bool moved;
0607         do {
0608             moved = false;
0609             for (ExpoCell *cell : std::as_const(m_cells)) {
0610                 QRect oldRect;
0611                 QRect *target = &targets[cell];
0612                 // This may cause some slight distortion if the windows are enlarged a large amount
0613                 int widthDiff = m_accuracy;
0614                 int heightDiff = heightForWidth(cell, target->width() + widthDiff) - target->height();
0615                 int xDiff = widthDiff / 2; // Also move a bit in the direction of the enlarge, allows the
0616                 int yDiff = heightDiff / 2; // center windows to be enlarged if there is gaps on the side.
0617 
0618                 // heightDiff (and yDiff) will be re-computed after each successful enlargement attempt
0619                 // so that the error introduced in the window's aspect ratio is minimized
0620 
0621                 // Attempt enlarging to the top-right
0622                 oldRect = *target;
0623                 target->setRect(target->x() + xDiff,
0624                                 target->y() - yDiff - heightDiff,
0625                                 target->width() + widthDiff,
0626                                 target->height() + heightDiff);
0627                 if (isOverlappingAny(cell, targets, borderRegion, m_spacing)) {
0628                     *target = oldRect;
0629                 } else {
0630                     moved = true;
0631                     heightDiff = heightForWidth(cell, target->width() + widthDiff) - target->height();
0632                     yDiff = heightDiff / 2;
0633                 }
0634 
0635                 // Attempt enlarging to the bottom-right
0636                 oldRect = *target;
0637                 target->setRect(target->x() + xDiff,
0638                                 target->y() + yDiff,
0639                                 target->width() + widthDiff,
0640                                 target->height() + heightDiff);
0641                 if (isOverlappingAny(cell, targets, borderRegion, m_spacing)) {
0642                     *target = oldRect;
0643                 } else {
0644                     moved = true;
0645                     heightDiff = heightForWidth(cell, target->width() + widthDiff) - target->height();
0646                     yDiff = heightDiff / 2;
0647                 }
0648 
0649                 // Attempt enlarging to the bottom-left
0650                 oldRect = *target;
0651                 target->setRect(target->x() - xDiff - widthDiff,
0652                                 target->y() + yDiff,
0653                                 target->width() + widthDiff,
0654                                 target->height() + heightDiff);
0655                 if (isOverlappingAny(cell, targets, borderRegion, m_spacing)) {
0656                     *target = oldRect;
0657                 } else {
0658                     moved = true;
0659                     heightDiff = heightForWidth(cell, target->width() + widthDiff) - target->height();
0660                     yDiff = heightDiff / 2;
0661                 }
0662 
0663                 // Attempt enlarging to the top-left
0664                 oldRect = *target;
0665                 target->setRect(target->x() - xDiff - widthDiff,
0666                                 target->y() - yDiff - heightDiff,
0667                                 target->width() + widthDiff,
0668                                 target->height() + heightDiff);
0669                 if (isOverlappingAny(cell, targets, borderRegion, m_spacing)) {
0670                     *target = oldRect;
0671                 } else {
0672                     moved = true;
0673                 }
0674             }
0675         } while (moved);
0676 
0677         // The expanding code above can actually enlarge windows over 1.0/2.0 scale, we don't like this
0678         // We can't add this to the loop above as it would cause a never-ending loop so we have to make
0679         // do with the less-than-optimal space usage with using this method.
0680         for (ExpoCell *cell : std::as_const(m_cells)) {
0681             QRect *target = &targets[cell];
0682             qreal scale = target->width() / qreal(cell->naturalWidth());
0683             if (scale > 2.0 || (scale > 1.0 && (cell->naturalWidth() > 300 || cell->naturalHeight() > 300))) {
0684                 scale = (cell->naturalWidth() > 300 || cell->naturalHeight() > 300) ? 1.0 : 2.0;
0685                 target->setRect(target->center().x() - int(cell->naturalWidth() * scale) / 2,
0686                                 target->center().y() - int(cell->naturalHeight() * scale) / 2,
0687                                 cell->naturalWidth() * scale,
0688                                 cell->naturalHeight() * scale);
0689             }
0690         }
0691     }
0692 
0693     for (ExpoCell *cell : std::as_const(m_cells)) {
0694         const QRect &cellRect = targets.value(cell);
0695         QRect cellRectWithoutMargins = cellRect.marginsRemoved(cell->margins());
0696         if (!cellRectWithoutMargins.isValid()) {
0697             cellRectWithoutMargins = cellRect;
0698         }
0699         const QRect rect = centered(cell, cellRectWithoutMargins);
0700 
0701         cell->setX(rect.x());
0702         cell->setY(rect.y());
0703         cell->setWidth(rect.width());
0704         cell->setHeight(rect.height());
0705     }
0706 }
0707 
0708 void ExpoLayout::resetTransformations()
0709 {
0710     for (ExpoCell *cell : std::as_const(m_cells)) {
0711         cell->setX(cell->naturalX());
0712         cell->setY(cell->naturalY());
0713         cell->setWidth(cell->naturalWidth());
0714         cell->setHeight(cell->naturalHeight());
0715     }
0716 }
0717 
0718 #include "moc_expolayout.cpp"