Use a better strategy for size propagation when resizing via separator

Behaves like old kddockwidgets now.
When dragging a separator, only the immediate neighbours are resized.
Only when min-sizes are violated that it propagates.
This commit is contained in:
Sergio Martins
2020-05-09 17:42:55 +01:00
parent ed6d5134b0
commit a9d0ec3fdd
3 changed files with 112 additions and 31 deletions

View File

@@ -1659,12 +1659,10 @@ void ItemContainer::resizeChildren(QSize oldSize, QSize newSize, SizingInfo::Lis
remaining = qAbs(remaining); // Easier to deal in positive numbers
const bool isSide1 = strategy == ChildrenResizeStrategy::Side1;
const int start = isSide1 ? 0 : count - 1;
const int end = isSide1 ? count - 1 : 0;
const int increment = isSide1 ? 1 : -1;
for (int i = 0; i < count; i++) {
const int index = isSide1 ? i : count - 1 - i;
for (int i = start; i <= end; i+=increment) {
SizingInfo &size = childSizes[i];
SizingInfo &size = childSizes[index];
if (isGrowing) {
// Since we don't honour item max-size yet, it can just grow all it wants
@@ -2189,6 +2187,7 @@ void ItemContainer::growItem(int index, SizingInfo::List &sizes, int missing,
int side1Growth = 0;
int side2Growth = 0;
auto neighbourSqueezeStrategy = NeighbourSqueezeStrategy::Equally;
if (growthStrategy == GrowthStrategy::BothSidesEqually) {
const int count = sizes.count();
@@ -2237,12 +2236,14 @@ void ItemContainer::growItem(int index, SizingInfo::List &sizes, int missing,
} else if (growthStrategy == GrowthStrategy::Side1Only) {
side1Growth = missing;
side2Growth = 0;
neighbourSqueezeStrategy = NeighbourSqueezeStrategy::Side2NeighboursFirst;
} else if (growthStrategy == GrowthStrategy::Side2Only) {
side1Growth = 0;
side2Growth = missing;
neighbourSqueezeStrategy = NeighbourSqueezeStrategy::Side1NeighboursFirst;
}
shrinkNeighbours(index, sizes, side1Growth, side2Growth);
shrinkNeighbours(index, sizes, side1Growth, side2Growth, neighbourSqueezeStrategy);
}
void ItemContainer::growItem(Item *item, int amount, GrowthStrategy growthStrategy, bool accountForNewSeparator)
@@ -2284,7 +2285,8 @@ SizingInfo::List ItemContainer::sizes(bool ignoreBeingInserted) const
}
QVector<int> ItemContainer::calculateSqueezes(SizingInfo::List::ConstIterator begin,
SizingInfo::List::ConstIterator end, int needed) const
SizingInfo::List::ConstIterator end, int needed,
NeighbourSqueezeStrategy strategy) const
{
QVector<int> availabilities;
for (auto it = begin; it < end; ++it) {
@@ -2295,38 +2297,59 @@ QVector<int> ItemContainer::calculateSqueezes(SizingInfo::List::ConstIterator be
QVector<int> squeezes(count, 0);
int missing = needed;
while (missing > 0) {
const int numDonors = std::count_if(availabilities.cbegin(), availabilities.cend(), [] (int num) {
return num > 0;
});
if (numDonors == 0) {
root()->dumpLayout();
Q_ASSERT(false);
return {};
if (strategy == NeighbourSqueezeStrategy::Equally) {
while (missing > 0) {
const int numDonors = std::count_if(availabilities.cbegin(), availabilities.cend(), [] (int num) {
return num > 0;
});
if (numDonors == 0) {
root()->dumpLayout();
Q_ASSERT(false);
return {};
}
int toTake = missing / numDonors;
if (toTake == 0)
toTake = missing;
for (int i = 0; i < count; ++i) {
const int available = availabilities.at(i);
if (available == 0)
continue;
const int took = qMin(toTake, available);
availabilities[i] -= took;
missing -= took;
squeezes[i] += took;
if (missing == 0)
break;
}
}
} else if (strategy == NeighbourSqueezeStrategy::Side1NeighboursFirst ||
strategy == NeighbourSqueezeStrategy::Side2NeighboursFirst) {
int toTake = missing / numDonors;
if (toTake == 0)
toTake = missing;
for (int i = 0; i < count; i++) {
const int index = strategy == NeighbourSqueezeStrategy::Side1NeighboursFirst ? i
: count - 1 - i;
const int available = availabilities.at(index);
if (available > 0) {
const int took = qMin(missing, available);
missing -= took;
squeezes[index] += took;
}
for (int i = 0; i < count; ++i) {
const int available = availabilities.at(i);
if (available == 0)
continue;
const int took = qMin(toTake, available);
availabilities[i] -= took;
missing -= took;
squeezes[i] += took;
if (missing == 0)
break;
}
}
return squeezes;
}
void ItemContainer::shrinkNeighbours(int index, SizingInfo::List &sizes, int side1Amount, int side2Amount)
void ItemContainer::shrinkNeighbours(int index, SizingInfo::List &sizes, int side1Amount,
int side2Amount, NeighbourSqueezeStrategy strategy)
{
Q_ASSERT(side1Amount > 0 || side2Amount > 0);
Q_ASSERT(side1Amount >= 0 && side2Amount >= 0); // never negative
@@ -2335,7 +2358,7 @@ void ItemContainer::shrinkNeighbours(int index, SizingInfo::List &sizes, int sid
auto begin = sizes.cbegin();
auto end = sizes.cbegin() + index;
const QVector<int> squeezes = calculateSqueezes(begin, end, side1Amount);
const QVector<int> squeezes = calculateSqueezes(begin, end, side1Amount, strategy);
for (int i = 0; i < squeezes.size(); ++i) {
const int squeeze = squeezes.at(i);
SizingInfo &sizing = sizes[i];
@@ -2348,7 +2371,7 @@ void ItemContainer::shrinkNeighbours(int index, SizingInfo::List &sizes, int sid
auto begin = sizes.cbegin() + index + 1;
auto end = sizes.cend();
const QVector<int> squeezes = calculateSqueezes(begin, end, side2Amount);
const QVector<int> squeezes = calculateSqueezes(begin, end, side2Amount, strategy);
for (int i = 0; i < squeezes.size(); ++i) {
const int squeeze = squeezes.at(i);
SizingInfo &sizing = sizes[i + index + 1];

View File

@@ -80,6 +80,12 @@ enum class ChildrenResizeStrategy {
Side2, ///< When resizing a container, it takes/adds space from Side2 children first
};
enum class NeighbourSqueezeStrategy {
Equally, ///< The squeeze is spread between all neighbours, not just immediate ones first
Side1NeighboursFirst, ///< The first neighbour takes as much squeeze as it cans, only then the next neighbour is squezed, and so forth
Side2NeighboursFirst ///< Same as Side1NeighboursFirst but does reverse order
};
inline Qt::Orientation oppositeOrientation(Qt::Orientation o) {
return o == Qt::Vertical ? Qt::Horizontal
: Qt::Vertical;
@@ -469,7 +475,8 @@ public:
/// The neighbours at the left/top of the item, will be shrunk by @p side1Amount, while the items
/// at right/bottom will be shrunk by @p side2Amount.
/// Squeezes all the neighbours (not just the immediate ones).
void shrinkNeighbours(int index, SizingInfo::List &sizes, int side1Amount, int side2Amount);
void shrinkNeighbours(int index, SizingInfo::List &sizes, int side1Amount, int side2Amount,
NeighbourSqueezeStrategy = NeighbourSqueezeStrategy::Equally);
Item *visibleNeighbourFor(const Item *item, Side side) const;
QSize availableSize() const;
@@ -484,7 +491,9 @@ public:
void onChildVisibleChanged(Item *child, bool visible);
void updateSizeConstraints();
SizingInfo::List sizes(bool ignoreBeingInserted = false) const;
QVector<int> calculateSqueezes(SizingInfo::List::ConstIterator begin, SizingInfo::List::ConstIterator end, int needed) const;
QVector<int> calculateSqueezes(SizingInfo::List::ConstIterator begin,
SizingInfo::List::ConstIterator end,
int needed, NeighbourSqueezeStrategy) const;
QRect suggestedDropRect(QSize minSize, const Item *relativeTo, Location) const;
void positionItems();
void positionItems(SizingInfo::List &sizes);

View File

@@ -198,6 +198,7 @@ private Q_SLOTS:
void tst_insertHiddenContainer();
void tst_availableOnSide();
void tst_resizeViaSeparator();
void tst_resizeViaSeparator2();
void tst_mapToRoot();
};
@@ -1178,6 +1179,54 @@ void TestMultiSplitter::tst_resizeViaSeparator()
QCOMPARE(separator->position(), oldPos -delta);
}
void TestMultiSplitter::tst_resizeViaSeparator2()
{
// Here we resize one of the separators and make sure onyly the items next to the separator move
// propagation should only start when constraints have been met
auto root = createRoot();
auto item1 = createItem();
auto item2 = createItem();
auto item3 = createItem();
auto item4 = createItem();
root->insertItem(item1, Location_OnLeft);
root->insertItem(item2, Location_OnRight);
root->insertItem(item3, Location_OnRight);
root->insertItem(item4, Location_OnRight);
auto resizeChildrenTo1000px = [&root] {
/// Make sure each item has 1000 of width. Cheating here as we don't have API to resize all.
const int numChildren = root->numChildren();
for (auto item : qAsConst(root->m_children)) {
item->m_sizingInfo.percentageWithinParent = 1.0 / numChildren;
}
root->setSize_recursive(QSize(4000 + Item::separatorThickness*(numChildren-1), 1000));
};
const int delta = 100;
const int originalChildWidth = 1000;
resizeChildrenTo1000px();
const auto separators = root->separators_recursive();
QVERIFY(root->checkSanity());
QCOMPARE(separators.size(), 3);
root->requestSeparatorMove(separators[1], delta);
QCOMPARE(item1->width(), originalChildWidth); // item1 didn't change when we moved the second separator, only item2 and 3 are supposed to move
QCOMPARE(item2->width(), originalChildWidth + delta);
QCOMPARE(item3->width(), originalChildWidth - delta);
QCOMPARE(item4->width(), originalChildWidth);
// And back
root->requestSeparatorMove(separators[1], -delta);
QCOMPARE(item1->width(), originalChildWidth); // item1 didn't change when we moved the second separator, only item2 and 3 are supposed to move
QCOMPARE(item2->width(), originalChildWidth);
QCOMPARE(item3->width(), originalChildWidth);
QCOMPARE(item4->width(), originalChildWidth);
}
void TestMultiSplitter::tst_mapToRoot()
{
auto root = createRoot();