Flow Layout fixes (#5515)

* Flow Layout fixes.

* Remove some comments.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-01-25 04:17:39 +01:00 committed by GitHub
parent f428148f64
commit 4e96157091
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 465 additions and 462 deletions

View file

@ -63,8 +63,6 @@ set(cockatrice_SOURCES
src/game/filters/filter_tree.cpp
src/game/filters/filter_tree_model.cpp
src/client/ui/layouts/flow_layout.cpp
src/client/ui/layouts/horizontal_flow_layout.cpp
src/client/ui/layouts/vertical_flow_layout.cpp
src/client/ui/widgets/general/layout_containers/flow_widget.cpp
src/game/game_scene.cpp
src/game/game_selector.cpp

View file

@ -20,8 +20,12 @@
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
FlowLayout::FlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
: QLayout(parent), horizontalMargin(hSpacing), verticalMargin(vSpacing)
FlowLayout::FlowLayout(QWidget *parent,
const Qt::Orientation _flowDirection,
const int margin,
const int hSpacing,
const int vSpacing)
: QLayout(parent), flowDirection(_flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
@ -62,6 +66,7 @@ bool FlowLayout::hasHeightForWidth() const
*/
int FlowLayout::heightForWidth(const int width) const
{
if (flowDirection == Qt::Vertical) {
int height = 0;
int rowWidth = 0;
int rowHeight = 0;
@ -72,17 +77,40 @@ int FlowLayout::heightForWidth(const int width) const
}
int itemWidth = item->sizeHint().width() + horizontalSpacing();
if (rowWidth + itemWidth > width) { // Start a new row if the row width exceeds available width
if (rowWidth + itemWidth > width) {
height += rowHeight + verticalSpacing();
rowWidth = itemWidth;
rowHeight = item->sizeHint().height() + verticalSpacing();
rowHeight = item->sizeHint().height();
} else {
rowWidth += itemWidth;
rowHeight = qMax(rowHeight, item->sizeHint().height());
}
}
height += rowHeight; // Add the final row's height
height += rowHeight; // Add height of the last row
return height;
} else {
int width = 0;
int colWidth = 0;
int colHeight = 0;
for (const QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
int itemHeight = item->sizeHint().height();
if (colHeight + itemHeight > width) {
width += colWidth;
colHeight = itemHeight;
colWidth = item->sizeHint().width();
} else {
colHeight += itemHeight;
colWidth = qMax(colWidth, item->sizeHint().width());
}
}
width += colWidth; // Add width of the last column
return width;
}
}
/**
@ -93,132 +121,420 @@ void FlowLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle.
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom); // Retrieves the layout's content margins.
if (flowDirection == Qt::Horizontal) {
// If we have a parent scroll area, we're clamped to that, else we use our own rectangle.
const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth();
// Adjust the rectangle to exclude margins.
const QRect adjustedRect = rect.adjusted(+left, +top, -right, -bottom);
const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth);
// Calculate the available width for items, considering either the adjusted rectangle's width
// or the parent scroll area width, if applicable.
const int availableWidth = qMax(adjustedRect.width(), getParentScrollAreaWidth());
// Arrange all rows of items within the available width and get the total height used.
const int totalHeight = layoutAllRows(adjustedRect.x(), adjustedRect.y(), availableWidth);
// If the layout's parent is a QWidget, update its minimum size to ensure it can accommodate
// the arranged items' dimensions.
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setMinimumSize(availableWidth, totalHeight);
parentWidgetPtr->setFixedSize(availableWidth, totalHeight);
}
} else {
const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setFixedSize(totalWidth, availableHeight);
}
}
}
/**
* @brief Arranges items in rows based on the available width.
* Items are added to a row until the row's width exceeds `availableWidth`.
* Then, a new row is started.
* @param originX The starting x-coordinate for the row layout.
* @param originY The starting y-coordinate for the row layout.
* @param availableWidth The available width to lay out items.
* @return The y-coordinate of the final row's end position.
* @brief Lays out items into rows according to the available width, starting from a given origin.
* Each row is arranged within `availableWidth`, wrapping to a new row as necessary.
* @param originX The x-coordinate for the layout start position.
* @param originY The y-coordinate for the layout start position.
* @param availableWidth The width within which each row is constrained.
* @return The total height after arranging all rows.
*/
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
{
QVector<QLayoutItem *> rowItems; // Temporary storage for items in the current row.
int currentXPosition = originX; // Tracks the x-coordinate for placing items in the current row.
int currentYPosition = originY; // Tracks the y-coordinate, updated after each row.
QVector<QLayoutItem *> rowItems; // Holds items for the current row
int currentXPosition = originX; // Tracks the x-coordinate while placing items
int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row
int rowHeight = 0; // Tracks the maximum height of items in the current row.
int rowHeight = 0; // Tracks the maximum height of items in the current row
// Iterate through all layout items to arrange them.
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the item.
const int itemWidth = itemSize.width() + horizontalSpacing();
QSize itemSize = item->sizeHint(); // The suggested size for the current item
int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
// Check if the item fits in the current row's remaining width.
// Check if the current item fits in the remaining width of the current row
if (currentXPosition + itemWidth > availableWidth) {
// If not, layout the current row and start a new row.
// If not, layout the current row and start a new row
layoutSingleRow(rowItems, originX, currentYPosition);
rowItems.clear(); // Clear the temporary storage for the new row.
currentXPosition = originX; // Reset x-position to the start of the new row.
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down for the new row.
rowHeight = 0; // Reset row height for the new row.
rowItems.clear(); // Reset the list for the new row
currentXPosition = originX; // Reset x-position to the row's start
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row
rowHeight = 0; // Reset row height for the new row
}
// Add the item to the current row.
// Add the item to the current row
rowItems.append(item);
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row height to the tallest item.
currentXPosition += itemSize.width() + horizontalSpacing(); // Move x-position for the next item.
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item
currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item
}
// Layout the final row if there are remaining items.
// Layout the final row if there are any remaining items
layoutSingleRow(rowItems, originX, currentYPosition);
currentYPosition += rowHeight; // Add the final row's height
return currentYPosition;
// Return the total height used, including the last row's height
return currentYPosition + rowHeight;
}
/**
* @brief Helper function for arranging a single row of items within specified bounds.
* @param rowItems Items to be arranged in the row.
* @param x The x-coordinate for starting the row.
* @param y The y-coordinate for starting the row.
* @brief Arranges a single row of items within specified x and y starting positions.
* @param rowItems A list of items to be arranged in the row.
* @param x The starting x-coordinate for the row.
* @param y The starting y-coordinate for the row.
*/
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
{
// Iterate through each item in the row and position it.
for (QLayoutItem *item : rowItems) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemMaxSize = item->widget()->maximumSize(); // Get the item's maximum allowable size.
// Constrain the item's width and height to its size hint or maximum size.
int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the calculated size and position.
// Get the maximum allowed size for the item
QSize itemMaxSize = item->widget()->maximumSize();
// Constrain the item's width and height to its size hint or maximum size
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the computed size and position
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the x-position for the next item, including horizontal spacing.
// Move the x-position to the right, leaving space for horizontal spacing
x += itemWidth + horizontalSpacing();
}
}
/**
* @brief Returns the preferred size for this layout.
* @return The maximum of all item size hints as a QSize.
* @brief Lays out items into columns according to the available height, starting from a given origin.
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
* @param originX The x-coordinate for the layout start position.
* @param originY The y-coordinate for the layout start position.
* @param availableHeight The height within which each column is constrained.
* @return The total width after arranging all columns.
*/
QSize FlowLayout::sizeHint() const
int FlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
{
QSize size;
for (const QLayoutItem *item : items) {
if (item != nullptr && !item->isEmpty()) {
size = size.expandedTo(item->sizeHint());
QVector<QLayoutItem *> colItems; // Holds items for the current column
int currentXPosition = originX; // Tracks the x-coordinate while placing items
int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
int colWidth = 0; // Tracks the maximum width of items in the current column
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the current item
// Check if the current item fits in the remaining height of the current column
if (currentYPosition + itemSize.height() > availableHeight) {
// If not, layout the current column and start a new column
layoutSingleColumn(colItems, currentXPosition, originY);
colItems.clear(); // Reset the list for the new column
currentYPosition = originY; // Reset y-position to the column's start
currentXPosition += colWidth; // Move x-position to the next column
colWidth = 0; // Reset column width for the new column
}
return size.isValid() ? size : QSize(0, 0);
// Add the item to the current column
colItems.append(item);
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
currentYPosition += itemSize.height(); // Move y-position for the next item
}
// Layout the final column if there are any remaining items
layoutSingleColumn(colItems, currentXPosition, originY);
// Return the total width used, including the last column's width
return currentXPosition + colWidth;
}
/**
* @brief Returns the minimum size required to display all layout items.
* @return The minimum QSize needed by the layout.
* @brief Arranges a single column of items within specified x and y starting positions.
* @param colItems A list of items to be arranged in the column.
* @param x The starting x-coordinate for the column.
* @param y The starting y-coordinate for the column.
*/
void FlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
{
for (QLayoutItem *item : colItems) {
if (item == nullptr) {
qCDebug(FlowLayoutLog) << "Item is null.";
continue;
}
if (item->isEmpty()) {
qCDebug(FlowLayoutLog) << "Skipping empty item.";
continue;
}
// Debugging: Print the item's widget class name and size hint
QWidget *widget = item->widget();
if (widget) {
qCDebug(FlowLayoutLog) << "Widget class:" << widget->metaObject()->className();
qCDebug(FlowLayoutLog) << "Widget size hint:" << widget->sizeHint();
qCDebug(FlowLayoutLog) << "Widget maximum size:" << widget->maximumSize();
qCDebug(FlowLayoutLog) << "Widget minimum size:" << widget->minimumSize();
// Debugging: Print child widgets
const QObjectList &children = widget->children();
qCDebug(FlowLayoutLog) << "Child widgets:";
for (QObject *child : children) {
if (QWidget *childWidget = qobject_cast<QWidget *>(child)) {
qCDebug(FlowLayoutLog) << " - Child widget class:" << childWidget->metaObject()->className();
qCDebug(FlowLayoutLog) << " Size hint:" << childWidget->sizeHint();
qCDebug(FlowLayoutLog) << " Maximum size:" << childWidget->maximumSize();
}
}
} else {
qCDebug(FlowLayoutLog) << "Item does not have a widget.";
}
// Get the maximum allowed size for the item
QSize itemMaxSize = widget->maximumSize();
// Constrain the item's width and height to its size hint or maximum size
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Debugging: Print the computed geometry
qCDebug(FlowLayoutLog) << "Computed geometry: x=" << x << ", y=" << y << ", width=" << itemWidth
<< ", height=" << itemHeight;
// Set the item's geometry based on the computed size and position
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the y-position down by the item's height to place the next item below
y += itemHeight;
}
}
/**
* @brief Calculates the preferred size of the layout based on the flow direction.
* @return A QSize representing the ideal dimensions of the layout.
*/
QSize FlowLayout::sizeHint() const
{
if (flowDirection == Qt::Horizontal) {
return calculateSizeHintHorizontal();
} else {
return calculateSizeHintVertical();
}
}
/**
* @brief Calculates the minimum size required by the layout based on the flow direction.
* @return A QSize representing the minimum required dimensions.
*/
QSize FlowLayout::minimumSize() const
{
QSize size;
if (flowDirection == Qt::Horizontal) {
return calculateMinimumSizeHorizontal();
} else {
return calculateMinimumSizeVertical();
}
}
/**
* @brief Calculates the size hint for horizontal flow direction.
* @return A QSize representing the preferred dimensions.
*/
QSize FlowLayout::calculateSizeHintHorizontal() const
{
int maxWidth = 0; // Tracks the maximum width needed
int totalHeight = 0; // Tracks the total height across all rows
int rowHeight = 0; // Tracks the height of the current row
int currentWidth = 0; // Tracks the current row's width
const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
qCDebug(FlowLayoutLog) << "Calculating horizontal size hint. Available width:" << availableWidth;
for (const QLayoutItem *item : items) {
if (item != nullptr && !item->isEmpty()) {
size = size.expandedTo(item->minimumSize());
}
if (!item || item->isEmpty()) {
qCDebug(FlowLayoutLog) << "Skipping empty item.";
continue;
}
size.setWidth(qMin(size.width(), getParentScrollAreaWidth()));
size.setHeight(qMin(size.height(), getParentScrollAreaHeight()));
QSize itemSize = item->sizeHint();
int itemWidth = itemSize.width() + horizontalSpacing();
qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize << "Width with spacing:" << itemWidth;
return size.isValid() ? size : QSize(0, 0);
if (currentWidth + itemWidth > availableWidth) {
qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight;
maxWidth = qMax(maxWidth, currentWidth);
totalHeight += rowHeight + verticalSpacing();
qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth;
currentWidth = 0;
rowHeight = 0;
}
currentWidth += itemWidth;
rowHeight = qMax(rowHeight, itemSize.height());
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
}
// Account for the final row
maxWidth = qMax(maxWidth, currentWidth);
totalHeight += rowHeight;
qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth;
return QSize(maxWidth, totalHeight);
}
/**
* @brief Calculates the minimum size for horizontal flow direction.
* @return A QSize representing the minimum required dimensions.
*/
QSize FlowLayout::calculateMinimumSizeHorizontal() const
{
int maxWidth = 0; // Tracks the maximum width of a row
int totalHeight = 0; // Tracks the total height across all rows
int rowHeight = 0; // Tracks the height of the current row
int currentWidth = 0; // Tracks the current row's width
const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
qCDebug(FlowLayoutLog) << "Calculating horizontal minimum size. Available width:" << availableWidth;
for (const QLayoutItem *item : items) {
if (!item || item->isEmpty()) {
qCDebug(FlowLayoutLog) << "Skipping empty item.";
continue;
}
QSize itemMinSize = item->minimumSize();
int itemWidth = itemMinSize.width() + horizontalSpacing();
qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize << "Width with spacing:" << itemWidth;
if (currentWidth + itemWidth > availableWidth) {
qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight;
maxWidth = qMax(maxWidth, currentWidth);
totalHeight += rowHeight + verticalSpacing();
qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth;
currentWidth = 0;
rowHeight = 0;
}
currentWidth += itemWidth;
rowHeight = qMax(rowHeight, itemMinSize.height());
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
}
// Account for the final row
maxWidth = qMax(maxWidth, currentWidth);
totalHeight += rowHeight;
qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth;
return QSize(maxWidth, totalHeight);
}
/**
* @brief Calculates the size hint for vertical flow direction.
* @return A QSize representing the preferred dimensions.
*/
QSize FlowLayout::calculateSizeHintVertical() const
{
int totalWidth = 0;
int maxHeight = 0;
int colWidth = 0;
int currentHeight = 0;
const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight());
qCDebug(FlowLayoutLog) << "Calculating vertical size hint. Available height:" << availableHeight;
for (const QLayoutItem *item : items) {
if (!item || item->isEmpty()) {
qCDebug(FlowLayoutLog) << "Skipping empty item.";
continue;
}
QSize itemSize = item->sizeHint();
qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize;
if (currentHeight + itemSize.height() > availableHeight) {
qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight
<< "Column width:" << colWidth;
totalWidth += colWidth + horizontalSpacing();
maxHeight = qMax(maxHeight, currentHeight);
qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight;
currentHeight = 0;
colWidth = 0;
}
currentHeight += itemSize.height() + verticalSpacing();
colWidth = qMax(colWidth, itemSize.width());
qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth;
}
// Account for the final column
totalWidth += colWidth;
maxHeight = qMax(maxHeight, currentHeight);
qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight;
return QSize(totalWidth, maxHeight);
}
/**
* @brief Calculates the minimum size for vertical flow direction.
* @return A QSize representing the minimum required dimensions.
*/
QSize FlowLayout::calculateMinimumSizeVertical() const
{
int totalWidth = 0; // Tracks the total width across all columns
int maxHeight = 0; // Tracks the maximum height of a column
int colWidth = 0; // Tracks the width of the current column
int currentHeight = 0; // Tracks the current column's height
const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight());
qCDebug(FlowLayoutLog) << "Calculating vertical minimum size. Available height:" << availableHeight;
for (const QLayoutItem *item : items) {
if (!item || item->isEmpty()) {
qCDebug(FlowLayoutLog) << "Skipping empty item.";
continue;
}
QSize itemMinSize = item->minimumSize();
int itemHeight = itemMinSize.height() + verticalSpacing();
qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize
<< "Height with spacing:" << itemHeight;
if (currentHeight + itemHeight > availableHeight) {
qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight
<< "Column width:" << colWidth;
totalWidth += colWidth + horizontalSpacing();
maxHeight = qMax(maxHeight, currentHeight);
qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight;
currentHeight = 0;
colWidth = 0;
}
currentHeight += itemHeight;
colWidth = qMax(colWidth, itemMinSize.width());
qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth;
}
// Account for the final column
totalWidth += colWidth;
maxHeight = qMax(maxHeight, currentHeight);
qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight;
return QSize(totalWidth, maxHeight);
}
/**

View file

@ -3,16 +3,22 @@
#include <QLayout>
#include <QList>
#include <QLoggingCategory>
#include <QWidget>
#include <qstyle.h>
inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout");
class FlowLayout : public QLayout
{
public:
explicit FlowLayout(QWidget *parent = nullptr);
FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing);
FlowLayout(QWidget *parent, Qt::Orientation _flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0);
~FlowLayout() override;
QSize calculateMinimumSizeHorizontal() const;
QSize calculateSizeHintVertical() const;
QSize calculateMinimumSizeVertical() const;
void addItem(QLayoutItem *item) override;
[[nodiscard]] int count() const override;
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
@ -31,11 +37,15 @@ public:
void setGeometry(const QRect &rect) override;
virtual int layoutAllRows(int originX, int originY, int availableWidth);
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
int layoutAllColumns(int originX, int originY, int availableHeight);
void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
[[nodiscard]] QSize sizeHint() const override;
[[nodiscard]] QSize minimumSize() const override;
QSize calculateSizeHintHorizontal() const;
protected:
QList<QLayoutItem *> items; // List to store layout items
Qt::Orientation flowDirection;
int horizontalMargin;
int verticalMargin;
};

View file

@ -1,142 +0,0 @@
#include "horizontal_flow_layout.h"
/**
* @brief Constructs a HorizontalFlowLayout instance with the specified parent widget.
* This layout arranges items in columns within the given height, automatically adjusting its width.
* @param parent The parent widget to which this layout belongs.
* @param margin The layout margin.
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
HorizontalFlowLayout::HorizontalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
: FlowLayout(parent, margin, hSpacing, vSpacing)
{
}
/**
* @brief Destructor for HorizontalFlowLayout, responsible for cleaning up layout items.
*/
HorizontalFlowLayout::~HorizontalFlowLayout()
{
QLayoutItem *item;
while ((item = FlowLayout::takeAt(0))) {
delete item;
}
}
/**
* @brief Calculates the required width to display all items, given a specified height.
* This method arranges items into columns and determines the total width needed.
* @param height The available height for arranging layout items.
* @return The total width required to fit all items, organized in columns constrained by the given height.
*/
int HorizontalFlowLayout::heightForWidth(const int height) const
{
int width = 0;
int colWidth = 0;
int colHeight = 0;
for (const QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
int itemHeight = item->sizeHint().height();
if (colHeight + itemHeight > height) {
width += colWidth;
colHeight = itemHeight;
colWidth = item->sizeHint().width();
} else {
colHeight += itemHeight;
colWidth = qMax(colWidth, item->sizeHint().width());
}
}
width += colWidth; // Add width of the last column
return width;
}
/**
* @brief Sets the geometry of the layout items, arranging them in columns within the given height.
* @param rect The rectangle area defining the layout space.
*/
void HorizontalFlowLayout::setGeometry(const QRect &rect)
{
const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setMinimumSize(totalWidth, availableHeight);
}
}
/**
* @brief Lays out items into columns according to the available height, starting from a given origin.
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
* @param originX The x-coordinate for the layout start position.
* @param originY The y-coordinate for the layout start position.
* @param availableHeight The height within which each column is constrained.
* @return The total width after arranging all columns.
*/
int HorizontalFlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
{
QVector<QLayoutItem *> colItems; // Holds items for the current column
int currentXPosition = originX; // Tracks the x-coordinate while placing items
int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
int colWidth = 0; // Tracks the maximum width of items in the current column
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the current item
// Check if the current item fits in the remaining height of the current column
if (currentYPosition + itemSize.height() > availableHeight) {
// If not, layout the current column and start a new column
layoutSingleColumn(colItems, currentXPosition, originY);
colItems.clear(); // Reset the list for the new column
currentYPosition = originY; // Reset y-position to the column's start
currentXPosition += colWidth; // Move x-position to the next column
colWidth = 0; // Reset column width for the new column
}
// Add the item to the current column
colItems.append(item);
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
currentYPosition += itemSize.height(); // Move y-position for the next item
}
// Layout the final column if there are any remaining items
layoutSingleColumn(colItems, currentXPosition, originY);
// Return the total width used, including the last column's width
return currentXPosition + colWidth;
}
/**
* @brief Arranges a single column of items within specified x and y starting positions.
* @param colItems A list of items to be arranged in the column.
* @param x The starting x-coordinate for the column.
* @param y The starting y-coordinate for the column.
*/
void HorizontalFlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
{
for (QLayoutItem *item : colItems) {
if (item != nullptr && item->isEmpty()) {
continue;
}
// Get the maximum allowed size for the item
QSize itemMaxSize = item->widget()->maximumSize();
// Constrain the item's width and height to its size hint or maximum size
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the computed size and position
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the y-position down by the item's height to place the next item below
y += itemHeight;
}
}

View file

@ -1,19 +0,0 @@
#ifndef HORIZONTAL_FLOW_LAYOUT_H
#define HORIZONTAL_FLOW_LAYOUT_H
#include "flow_layout.h"
class HorizontalFlowLayout : public FlowLayout
{
public:
explicit HorizontalFlowLayout(QWidget *parent = nullptr, int margin = 0, int hSpacing = 0, int vSpacing = 0);
~HorizontalFlowLayout() override;
[[nodiscard]] int heightForWidth(int height) const override;
void setGeometry(const QRect &rect) override;
int layoutAllColumns(int originX, int originY, int availableHeight);
static void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
};
#endif // HORIZONTAL_FLOW_LAYOUT_H

View file

@ -1,144 +0,0 @@
#include "vertical_flow_layout.h"
/**
* @brief Constructs a VerticalFlowLayout instance with the specified parent widget.
* This layout arranges items in rows within the given width, automatically adjusting its height.
* @param parent The parent widget to which this layout belongs.
* @param margin The layout margin.
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
VerticalFlowLayout::VerticalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
: FlowLayout(parent, margin, hSpacing, vSpacing)
{
}
/**
* @brief Destructor for VerticalFlowLayout, responsible for cleaning up layout items.
*/
VerticalFlowLayout::~VerticalFlowLayout()
{
QLayoutItem *item;
while ((item = FlowLayout::takeAt(0))) {
delete item;
}
}
/**
* @brief Calculates the required height to display all items, given a specified width.
* This method arranges items into rows and determines the total height needed.
* @param width The available width for arranging layout items.
* @return The total height required to fit all items, organized in rows constrained by the given width.
*/
int VerticalFlowLayout::heightForWidth(const int width) const
{
int height = 0;
int rowWidth = 0;
int rowHeight = 0;
for (const QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
int itemWidth = item->sizeHint().width() + horizontalSpacing();
if (rowWidth + itemWidth > width) {
height += rowHeight + verticalSpacing();
rowWidth = itemWidth;
rowHeight = item->sizeHint().height();
} else {
rowWidth += itemWidth;
rowHeight = qMax(rowHeight, item->sizeHint().height());
}
}
height += rowHeight; // Add height of the last row
return height;
}
/**
* @brief Sets the geometry of the layout items, arranging them in rows within the given width.
* @param rect The rectangle area defining the layout space.
*/
void VerticalFlowLayout::setGeometry(const QRect &rect)
{
// If we have a parent scroll area, we're clamped to that, else we use our own rectangle.
const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth();
const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth);
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setMinimumSize(availableWidth, totalHeight);
}
}
/**
* @brief Lays out items into rows according to the available width, starting from a given origin.
* Each row is arranged within `availableWidth`, wrapping to a new row as necessary.
* @param originX The x-coordinate for the layout start position.
* @param originY The y-coordinate for the layout start position.
* @param availableWidth The width within which each row is constrained.
* @return The total height after arranging all rows.
*/
int VerticalFlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
{
QVector<QLayoutItem *> rowItems; // Holds items for the current row
int currentXPosition = originX; // Tracks the x-coordinate while placing items
int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row
int rowHeight = 0; // Tracks the maximum height of items in the current row
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the current item
int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
// Check if the current item fits in the remaining width of the current row
if (currentXPosition + itemWidth > availableWidth) {
// If not, layout the current row and start a new row
layoutSingleRow(rowItems, originX, currentYPosition);
rowItems.clear(); // Reset the list for the new row
currentXPosition = originX; // Reset x-position to the row's start
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row
rowHeight = 0; // Reset row height for the new row
}
// Add the item to the current row
rowItems.append(item);
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item
currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item
}
// Layout the final row if there are any remaining items
layoutSingleRow(rowItems, originX, currentYPosition);
// Return the total height used, including the last row's height
return currentYPosition + rowHeight;
}
/**
* @brief Arranges a single row of items within specified x and y starting positions.
* @param rowItems A list of items to be arranged in the row.
* @param x The starting x-coordinate for the row.
* @param y The starting y-coordinate for the row.
*/
void VerticalFlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
{
for (QLayoutItem *item : rowItems) {
if (item == nullptr || item->isEmpty()) {
continue;
}
// Get the maximum allowed size for the item
QSize itemMaxSize = item->widget()->maximumSize();
// Constrain the item's width and height to its size hint or maximum size
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the computed size and position
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the x-position to the right, leaving space for horizontal spacing
x += itemWidth + horizontalSpacing();
}
}

View file

@ -1,19 +0,0 @@
#ifndef VERTICAL_FLOW_LAYOUT_H
#define VERTICAL_FLOW_LAYOUT_H
#include "flow_layout.h"
class VerticalFlowLayout : public FlowLayout
{
public:
explicit VerticalFlowLayout(QWidget *parent = nullptr, int margin = 0, int hSpacing = 0, int vSpacing = 0);
~VerticalFlowLayout() override;
[[nodiscard]] int heightForWidth(int width) const override;
void setGeometry(const QRect &rect) override;
int layoutAllRows(int originX, int originY, int availableWidth) override;
void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y) override;
};
#endif // VERTICAL_FLOW_LAYOUT_H

View file

@ -5,10 +5,8 @@
#include "flow_widget.h"
#include "../../../layouts/horizontal_flow_layout.h"
#include "../../../layouts/vertical_flow_layout.h"
#include <QHBoxLayout>
#include <QResizeEvent>
#include <QWidget>
#include <qscrollarea.h>
#include <qsizepolicy.h>
@ -21,41 +19,53 @@
* @param verticalPolicy The vertical scroll bar policy for the scroll area.
*/
FlowWidget::FlowWidget(QWidget *parent,
const Qt::Orientation _flowDirection,
const Qt::ScrollBarPolicy horizontalPolicy,
const Qt::ScrollBarPolicy verticalPolicy)
: QWidget(parent)
: QWidget(parent), flowDirection(_flowDirection)
{
// Main Widget and Layout
this->setMinimumSize(0, 0);
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mainLayout = new QHBoxLayout();
if (_flowDirection == Qt::Horizontal) {
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
this->setMinimumWidth(0);
} else {
this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
this->setMinimumHeight(0);
}
mainLayout = new QHBoxLayout(this);
this->setLayout(mainLayout);
// Flow Layout inside the scroll area
container = new QWidget();
if (horizontalPolicy != Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
flowLayout = new HorizontalFlowLayout(container);
} else if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy != Qt::ScrollBarAlwaysOff) {
flowLayout = new VerticalFlowLayout(container);
} else {
flowLayout = new FlowLayout(container, 0, 0, 0);
}
container->setLayout(flowLayout);
// The container should expand as much as possible, trusting the scrollArea to constrain it.
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
container->setMinimumSize(0, 0);
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.
scrollArea = new QScrollArea();
scrollArea = new QScrollArea(this);
scrollArea->setWidgetResizable(true);
scrollArea->setMinimumSize(0, 0);
scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// Set scrollbar policies
scrollArea->setHorizontalScrollBarPolicy(horizontalPolicy);
scrollArea->setVerticalScrollBarPolicy(verticalPolicy);
} else {
scrollArea = nullptr;
}
// Flow Layout inside the scroll area
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
container = new QWidget(this);
} else {
container = new QWidget(scrollArea);
}
flowLayout = new FlowLayout(container, flowDirection);
container->setLayout(flowLayout);
// The container should expand as much as possible, trusting the scrollArea to constrain it.
if (_flowDirection == Qt::Horizontal) {
container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
container->setMinimumWidth(0);
} else {
container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
container->setMinimumHeight(0);
}
// Use the FlowLayout container directly if we disable the ScrollArea
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
@ -75,15 +85,6 @@ FlowWidget::FlowWidget(QWidget *parent,
*/
void FlowWidget::addWidget(QWidget *widget_to_add) const
{
// Adjust size policy if scrollbars are disabled
if (scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
widget_to_add->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
}
if (scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
widget_to_add->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
}
// Add the widget to the flow layout
flowLayout->addWidget(widget_to_add);
}
@ -106,15 +107,7 @@ void FlowWidget::clearLayout()
delete item; // Delete the layout item
}
} else {
if (scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff &&
scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
flowLayout = new HorizontalFlowLayout(container);
} else if (scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff &&
scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) {
flowLayout = new VerticalFlowLayout(container);
} else {
flowLayout = new FlowLayout(container, 0, 0, 0);
}
flowLayout = new FlowLayout(container, flowDirection);
container->setLayout(flowLayout);
}
}
@ -139,6 +132,8 @@ void FlowWidget::resizeEvent(QResizeEvent *event)
// Ensure the scroll area and its content adjust correctly
if (scrollArea != nullptr && scrollArea->widget() != nullptr) {
scrollArea->widget()->adjustSize();
} else {
container->adjustSize();
}
}

View file

@ -3,15 +3,22 @@
#include "../../../layouts/flow_layout.h"
#include <QHBoxLayout>
#include <QLoggingCategory>
#include <QWidget>
#include <qscrollarea.h>
inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget");
inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size");
class FlowWidget final : public QWidget
{
Q_OBJECT
public:
FlowWidget(QWidget *parent, Qt::ScrollBarPolicy horizontalPolicy, Qt::ScrollBarPolicy verticalPolicy);
FlowWidget(QWidget *parent,
Qt::Orientation orientation,
Qt::ScrollBarPolicy horizontalPolicy,
Qt::ScrollBarPolicy verticalPolicy);
void addWidget(QWidget *widget_to_add) const;
void removeWidget(QWidget *widgetToRemove) const;
void clearLayout();
@ -27,6 +34,7 @@ protected:
void resizeEvent(QResizeEvent *event) override;
private:
Qt::Orientation flowDirection;
QHBoxLayout *mainLayout;
FlowLayout *flowLayout;
QWidget *container;

View file

@ -45,7 +45,7 @@ PrintingSelector::PrintingSelector(QWidget *parent,
searchBar->setVisible(SettingsCache::instance().getPrintingSelectorSearchBarVisible());
layout->addWidget(searchBar);
flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getPrintingSelectorCardSize());

View file

@ -23,7 +23,7 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(DeckPreviewWi
connect(deckLoader, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags);
flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
for (const QString &tag : this->deckLoader->getTags()) {
flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag));
}

View file

@ -20,7 +20,7 @@ VisualDeckStorageTagFilterWidget::VisualDeckStorageTagFilterWidget(VisualDeckSto
setFixedHeight(100);
auto *flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
auto *flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
}

View file

@ -37,7 +37,7 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
layout->addLayout(searchAndSortLayout);
layout->addWidget(tagFilterWidget);
flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getVisualDeckStorageCardSize());