Properly calculate a lot of things related to these layouts. (#5817)

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-04-09 18:09:04 +02:00 committed by GitHub
parent 6661a5d946
commit 80b6d6a31f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 118 additions and 99 deletions

View file

@ -1,5 +1,6 @@
#include "overlap_layout.h" #include "overlap_layout.h"
#include <QDebug>
#include <QtMath> #include <QtMath>
/** /**
@ -32,9 +33,10 @@ OverlapLayout::OverlapLayout(QWidget *parent,
const int overlapPercentage, const int overlapPercentage,
const int maxColumns, const int maxColumns,
const int maxRows, const int maxRows,
const Qt::Orientation direction) const Qt::Orientation _overlapDirection,
const Qt::Orientation _flowDirection)
: QLayout(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows), : QLayout(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows),
direction(direction) overlapDirection(_overlapDirection), flowDirection(_flowDirection)
{ {
} }
@ -148,17 +150,21 @@ void OverlapLayout::setGeometry(const QRect &rect)
} }
// Calculate the overlap offsets based on the layout direction and overlap percentage. // Calculate the overlap offsets based on the layout direction and overlap percentage.
const int overlapOffsetWidth = (direction == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0; const int overlapOffsetWidth = (overlapDirection == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0;
const int overlapOffsetHeight = (direction == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0; const int overlapOffsetHeight = (overlapDirection == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0;
// Determine the number of columns based on layout constraints and available space. // Determine the number of columns based on layout constraints and available space.
int columns; int columns;
if (direction == Qt::Horizontal) { if (flowDirection == Qt::Horizontal) {
if (maxColumns > 0) { if (maxColumns > 0) {
// Calculate the maximum possible columns given the available width and overlap. // Calculate the maximum possible columns given the available width and overlap.
const int availableColumns = (availableWidth + overlapOffsetWidth) / (maxItemWidth - overlapOffsetWidth); const int availableColumns = (availableWidth + overlapOffsetWidth) / (maxItemWidth - overlapOffsetWidth);
// Use the smaller of maxColumns and availableColumns. // Use the smaller of maxColumns and availableColumns.
qCDebug(OverlapLayoutLog) << " Max Columns " << maxColumns << " available columns " << availableColumns;
columns = qMin(maxColumns, availableColumns); columns = qMin(maxColumns, availableColumns);
if (columns < 1) {
columns = 1;
}
} else { } else {
// If no maxColumns constraint, allow as many columns as possible. // If no maxColumns constraint, allow as many columns as possible.
columns = INT_MAX; columns = INT_MAX;
@ -170,12 +176,16 @@ void OverlapLayout::setGeometry(const QRect &rect)
// Determine the number of rows based on layout constraints and available space. // Determine the number of rows based on layout constraints and available space.
int rows; int rows;
if (direction == Qt::Vertical) { if (flowDirection == Qt::Vertical) {
if (maxRows > 0) { if (maxRows > 0) {
// Calculate the maximum possible rows given the available height and overlap. // Calculate the maximum possible rows given the available height and overlap.
const int availableRows = (availableHeight + overlapOffsetHeight) / (maxItemHeight - overlapOffsetHeight); const int availableRows = (availableHeight + overlapOffsetHeight) / (maxItemHeight - overlapOffsetHeight);
// Use the smaller of maxRows and availableRows. // Use the smaller of maxRows and availableRows.
qCDebug(OverlapLayoutLog) << " Max Rows " << maxRows << " available rows " << availableRows;
rows = qMin(maxRows, availableRows); rows = qMin(maxRows, availableRows);
if (rows < 1) {
rows = 1;
}
} else { } else {
// If no maxRows constraint, allow as many rows as possible. // If no maxRows constraint, allow as many rows as possible.
rows = INT_MAX; rows = INT_MAX;
@ -200,16 +210,17 @@ void OverlapLayout::setGeometry(const QRect &rect)
const int yPos = rect.top() + currentRow * (maxItemHeight - overlapOffsetHeight); const int yPos = rect.top() + currentRow * (maxItemHeight - overlapOffsetHeight);
item->setGeometry(QRect(xPos, yPos, maxItemWidth, maxItemHeight)); item->setGeometry(QRect(xPos, yPos, maxItemWidth, maxItemHeight));
// TODO: Figure this out properly or maybe adjust size hint to account for this?
// Update row and column indices based on the layout direction. // Update row and column indices based on the layout direction.
if (direction == Qt::Horizontal) { if (overlapDirection == Qt::Horizontal) {
currentColumn++; currentColumn++;
if (currentColumn >= columns) { if (currentColumn > qCeil(itemList.size() / rows)) {
currentColumn = 0; currentColumn = 0;
currentRow++; currentRow++;
} }
} else { } else {
currentRow++; currentRow++;
if (currentRow >= rows) { if (currentRow > qCeil(itemList.size() / columns)) {
currentRow = 0; currentRow = 0;
currentColumn++; currentColumn++;
} }
@ -223,88 +234,86 @@ void OverlapLayout::setGeometry(const QRect &rect)
*/ */
QSize OverlapLayout::calculatePreferredSize() const QSize OverlapLayout::calculatePreferredSize() const
{ {
// Get the parent widget for size and margin calculations.
const QWidget *parentWidget = this->parentWidget();
if (!parentWidget) {
return QSize(0, 0);
}
// Determine the maximum item dimensions. if (itemList.isEmpty()) {
return QSize(0, 0);
}
int availableWidth = parentWidget->width();
int availableHeight = parentWidget->height();
const QMargins margins = parentWidget->contentsMargins();
availableWidth -= margins.left() + margins.right();
availableHeight -= margins.top() + margins.bottom();
// Determine the maximum item width and height among all layout items.
int maxItemWidth = 0; int maxItemWidth = 0;
int maxItemHeight = 0; int maxItemHeight = 0;
for (QLayoutItem *item : itemList) { for (QLayoutItem *item : itemList) {
if (item == nullptr) { if (item != nullptr && item->widget()) {
continue; QSize itemSize = item->widget()->sizeHint();
maxItemWidth = qMax(maxItemWidth, itemSize.width());
maxItemHeight = qMax(maxItemHeight, itemSize.height());
} }
if (!item->widget()) {
continue;
}
QSize itemSize = item->widget()->sizeHint();
maxItemWidth = qMax(maxItemWidth, itemSize.width());
maxItemHeight = qMax(maxItemHeight, itemSize.height());
} }
// Calculate the overlap offsets. // Calculate the overlap offsets based on the layout direction and overlap percentage.
const int extra_for_overlap = (100 - overlapPercentage); const int overlapOffsetWidth = (overlapDirection == Qt::Horizontal) ? (maxItemWidth * overlapPercentage / 100) : 0;
const int overlapOffsetWidth = const int overlapOffsetHeight = (overlapDirection == Qt::Vertical) ? (maxItemHeight * overlapPercentage / 100) : 0;
(direction == Qt::Horizontal) ? qRound((maxItemWidth / 100.0) * extra_for_overlap) : 0;
const int overlapOffsetHeight =
(direction == Qt::Vertical) ? qRound((maxItemHeight / 100.0) * extra_for_overlap) : 0;
// Variables to hold the total dimensions based on layout orientation. // Determine the number of columns based on layout constraints and available space.
int totalWidth = 0; int columns;
int totalHeight = 0; if (flowDirection == Qt::Horizontal) {
if (maxColumns > 0) {
// Calculate the total size based on the layout direction and constraints. // Calculate the maximum possible columns given the available width and overlap.
if (direction == Qt::Horizontal) { const int availableColumns = (availableWidth + overlapOffsetWidth) / (maxItemWidth - overlapOffsetWidth);
// Determine the number of columns: // Use the smaller of maxColumns and availableColumns.
// - Use maxColumns if it is greater than 0 (constraint set by the user). qCDebug(OverlapLayoutLog) << " Max Columns " << maxColumns << " available columns " << availableColumns;
// - Otherwise, set the number of columns to the total number of items in the layout. columns = qMin(maxColumns, availableColumns);
const int numColumns = (maxColumns > 0) ? static_cast<int>(maxColumns) : static_cast<int>(itemList.size()); if (columns < 1) {
columns = 1;
// Calculate the extra space required for overlaps between columns: }
// - Each overlap reduces the effective width of subsequent items. } else {
const int extra_space_for_overlaps = overlapOffsetWidth * (numColumns - 1); // If no maxColumns constraint, allow as many columns as possible.
columns = INT_MAX;
// Total width: }
// - The first item's width is fully included (maxItemWidth). } else {
// - Add the space for all overlaps between adjacent items. // If not a horizontal layout, column count is irrelevant.
totalWidth = maxItemWidth + extra_space_for_overlaps; columns = INT_MAX;
// Determine the number of rows:
// - Use maxRows if maxColumns is set (to constrain the number of rows).
// - Otherwise, assume a single row.
const int numRows =
(maxColumns > 0) ? qCeil(static_cast<double>(itemList.size()) / static_cast<double>(numColumns)) : 1;
// Total height:
// - Multiply the number of rows by the item height (maxItemHeight).
// - Subtract the height overlap between rows to avoid double-counting.
totalHeight = maxItemHeight * numRows - (numRows - 1) * overlapOffsetHeight;
} else if (direction == Qt::Vertical) {
// Determine the number of columns:
// - Use maxRows to calculate how many columns are needed if a row constraint exists.
// - Otherwise, assume a single column.
const int numColumns =
(maxRows != 0) ? qCeil(static_cast<double>(itemList.size()) / static_cast<double>(maxRows)) : 1;
// Total width:
// - Multiply the number of columns by the item width (maxItemWidth).
// - Subtract the width overlap between columns to avoid double-counting.
totalWidth = maxItemWidth * numColumns - (numColumns - 1) * overlapOffsetWidth;
// Determine the number of rows:
// - Use maxRows if it is greater than 0 (constraint set by the user).
// - Otherwise, set the number of rows to the total number of items in the layout.
const int numRows = (maxRows > 0) ? static_cast<int>(maxRows) : static_cast<int>(itemList.size());
// Calculate the extra space required for overlaps between rows:
// - Each overlap reduces the effective height of subsequent items.
const int extraSpaceForOverlaps = overlapOffsetHeight * (numRows - 1);
// Total height:
// - The first item's height is fully included (maxItemHeight).
// - Add the space for all overlaps between adjacent items.
totalHeight = maxItemHeight + extraSpaceForOverlaps;
} }
return {totalWidth, totalHeight}; // Determine the number of rows based on layout constraints and available space.
int rows;
if (flowDirection == Qt::Vertical) {
if (maxRows > 0) {
// Calculate the maximum possible rows given the available height and overlap.
const int availableRows = (availableHeight + overlapOffsetHeight) / (maxItemHeight - overlapOffsetHeight);
// Use the smaller of maxRows and availableRows.
qCDebug(OverlapLayoutLog) << " Max Rows " << maxRows << " available rows " << availableRows;
rows = qMin(maxRows, availableRows);
if (rows < 1) {
rows = 1;
}
} else {
// If no maxRows constraint, allow as many rows as possible.
rows = INT_MAX;
}
} else {
// If not a vertical layout, row count is irrelevant.
rows = INT_MAX;
}
if (overlapDirection == Qt::Horizontal) {
return QSize(maxItemWidth + ((qCeil(itemList.size() / rows)) * (maxItemWidth - overlapOffsetWidth)),
rows * maxItemHeight);
} else {
return QSize(columns * maxItemWidth,
maxItemHeight + ((qCeil(itemList.size() / columns)) * (maxItemHeight - overlapOffsetHeight)));
}
} }
/** /**
@ -341,7 +350,7 @@ QSize OverlapLayout::minimumSize() const
*/ */
void OverlapLayout::setDirection(const Qt::Orientation _direction) void OverlapLayout::setDirection(const Qt::Orientation _direction)
{ {
direction = _direction; overlapDirection = _direction;
} }
/** /**
@ -378,7 +387,7 @@ void OverlapLayout::setMaxRows(const int _maxRows)
*/ */
int OverlapLayout::calculateMaxColumns() const int OverlapLayout::calculateMaxColumns() const
{ {
if (direction != Qt::Vertical || itemList.isEmpty()) { if (overlapDirection != Qt::Vertical || itemList.isEmpty()) {
return 1; // Only relevant if the layout direction is vertical return 1; // Only relevant if the layout direction is vertical
} }
@ -410,7 +419,7 @@ int OverlapLayout::calculateMaxColumns() const
*/ */
int OverlapLayout::calculateRowsForColumns(const int columns) const int OverlapLayout::calculateRowsForColumns(const int columns) const
{ {
if (direction != Qt::Vertical || itemList.isEmpty() || columns <= 0) { if (overlapDirection != Qt::Vertical || itemList.isEmpty() || columns <= 0) {
return 1; // Only relevant if the layout direction is vertical and there are items return 1; // Only relevant if the layout direction is vertical and there are items
} }
@ -429,7 +438,7 @@ int OverlapLayout::calculateRowsForColumns(const int columns) const
*/ */
int OverlapLayout::calculateMaxRows() const int OverlapLayout::calculateMaxRows() const
{ {
if (direction != Qt::Horizontal || itemList.isEmpty()) { if (overlapDirection != Qt::Horizontal || itemList.isEmpty()) {
return 1; // Only relevant if the layout direction is horizontal return 1; // Only relevant if the layout direction is horizontal
} }
@ -464,7 +473,7 @@ int OverlapLayout::calculateMaxRows() const
*/ */
int OverlapLayout::calculateColumnsForRows(const int rows) const int OverlapLayout::calculateColumnsForRows(const int rows) const
{ {
if (direction != Qt::Horizontal || itemList.isEmpty() || rows <= 0) { if (overlapDirection != Qt::Horizontal || itemList.isEmpty() || rows <= 0) {
return 1; // Only relevant if the layout direction is horizontal and there are items return 1; // Only relevant if the layout direction is horizontal and there are items
} }

View file

@ -3,8 +3,11 @@
#include <QLayout> #include <QLayout>
#include <QList> #include <QList>
#include <QLoggingCategory>
#include <QWidget> #include <QWidget>
inline Q_LOGGING_CATEGORY(OverlapLayoutLog, "overlap_layout");
class OverlapLayout : public QLayout class OverlapLayout : public QLayout
{ {
public: public:
@ -12,7 +15,8 @@ public:
int overlapPercentage = 10, int overlapPercentage = 10,
int maxColumns = 2, int maxColumns = 2,
int maxRows = 2, int maxRows = 2,
Qt::Orientation direction = Qt::Horizontal); Qt::Orientation overlapDirection = Qt::Vertical,
Qt::Orientation flowDirection = Qt::Horizontal);
~OverlapLayout(); ~OverlapLayout();
void addItem(QLayoutItem *item) override; void addItem(QLayoutItem *item) override;
@ -35,7 +39,8 @@ private:
int overlapPercentage; int overlapPercentage;
int maxColumns; int maxColumns;
int maxRows; int maxRows;
Qt::Orientation direction; Qt::Orientation overlapDirection;
Qt::Orientation flowDirection;
// Calculate the preferred size of the layout // Calculate the preferred size of the layout
QSize calculatePreferredSize() const; QSize calculatePreferredSize() const;

View file

@ -26,14 +26,14 @@ FlowWidget::FlowWidget(QWidget *parent,
{ {
// Main Widget and Layout // Main Widget and Layout
if (_flowDirection == Qt::Horizontal) { if (_flowDirection == Qt::Horizontal) {
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
this->setMinimumWidth(0); setMinimumWidth(0);
} else { } else {
this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
this->setMinimumHeight(0); setMinimumHeight(0);
} }
mainLayout = new QHBoxLayout(this); mainLayout = new QHBoxLayout(this);
this->setLayout(mainLayout); setLayout(mainLayout);
if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) { if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) {
// Scroll Area, which should expand as much as possible, since it should be the only direct child widget. // Scroll Area, which should expand as much as possible, since it should be the only direct child widget.

View file

@ -41,9 +41,8 @@ OverlapWidget::OverlapWidget(QWidget *parent,
: QWidget(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows), : QWidget(parent), overlapPercentage(overlapPercentage), maxColumns(maxColumns), maxRows(maxRows),
direction(direction), adjustOnResize(adjustOnResize) direction(direction), adjustOnResize(adjustOnResize)
{ {
this->setMinimumSize(0, 0); overlapLayout = new OverlapLayout(this, overlapPercentage, maxColumns, maxRows, direction, Qt::Horizontal);
overlapLayout = new OverlapLayout(this, overlapPercentage, maxColumns, maxRows, direction); setLayout(overlapLayout);
this->setLayout(overlapLayout);
} }
/** /**
@ -57,7 +56,12 @@ OverlapWidget::OverlapWidget(QWidget *parent,
*/ */
void OverlapWidget::addWidget(QWidget *widgetToAdd) const void OverlapWidget::addWidget(QWidget *widgetToAdd) const
{ {
this->overlapLayout->addWidget(widgetToAdd); overlapLayout->addWidget(widgetToAdd);
}
void OverlapWidget::removeWidget(QWidget *widgetToRemove) const
{
overlapLayout->removeWidget(widgetToRemove);
} }
/** /**
@ -72,8 +76,8 @@ void OverlapWidget::clearLayout()
if (overlapLayout != nullptr) { if (overlapLayout != nullptr) {
QLayoutItem *item; QLayoutItem *item;
while ((item = overlapLayout->takeAt(0)) != nullptr) { while ((item = overlapLayout->takeAt(0)) != nullptr) {
delete item->widget(); // Delete the widget item->widget()->deleteLater();
delete item; // Delete the layout item delete item;
} }
} }

View file

@ -17,6 +17,7 @@ public:
Qt::Orientation direction, Qt::Orientation direction,
bool adjustOnResize = false); bool adjustOnResize = false);
void addWidget(QWidget *widgetToAdd) const; void addWidget(QWidget *widgetToAdd) const;
void removeWidget(QWidget *widgetToRemove) const;
void clearLayout(); void clearLayout();
void adjustMaxColumnsAndRows(); void adjustMaxColumnsAndRows();