mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-06 05:23:56 -07:00
Fix and use FlowWidget/FlowLayout
Took 35 minutes Took 4 seconds
This commit is contained in:
parent
33e5ae00db
commit
80a1ba0ce9
8 changed files with 330 additions and 561 deletions
|
|
@ -1,7 +1,16 @@
|
||||||
/**
|
/**
|
||||||
* @file flow_layout.cpp
|
* @file flow_layout.cpp
|
||||||
* @brief Implementation of the FlowLayout class, a custom layout for dynamically organizing widgets
|
* @brief Implementation of FlowLayout — a QLayout that wraps child widgets into rows
|
||||||
* in rows within the constraints of available width or parent scroll areas.
|
* (Qt::Horizontal flow) or columns (Qt::Vertical flow).
|
||||||
|
*
|
||||||
|
* Design contract (following Qt layout conventions):
|
||||||
|
* - setGeometry() places children inside the given rect. Nothing else.
|
||||||
|
* - sizeHint() reports the unconstrained preferred size (all items in one line).
|
||||||
|
* - minimumSize() reports the minimum size (largest single item).
|
||||||
|
* - heightForWidth() reports the height needed for a given width (horizontal flow only).
|
||||||
|
*
|
||||||
|
* The layout never calls setFixedSize() or adjustSize() on its parent widget;
|
||||||
|
* that is the responsibility of the parent widget / scroll area.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "flow_layout.h"
|
#include "flow_layout.h"
|
||||||
|
|
@ -12,27 +21,18 @@
|
||||||
#include <QLayoutItem>
|
#include <QLayoutItem>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
|
#include <QWidgetItem>
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructs a FlowLayout instance with the specified parent widget, margin, and spacing values.
|
|
||||||
* @param parent The parent widget for this layout.
|
|
||||||
* @param margin The layout margin.
|
|
||||||
* @param hSpacing The horizontal spacing between items.
|
|
||||||
* @param vSpacing The vertical spacing between items.
|
|
||||||
*/
|
|
||||||
FlowLayout::FlowLayout(QWidget *parent,
|
FlowLayout::FlowLayout(QWidget *parent,
|
||||||
const Qt::Orientation _flowDirection,
|
const Qt::Orientation flowDirection,
|
||||||
const int margin,
|
const int margin,
|
||||||
const int hSpacing,
|
const int hSpacing,
|
||||||
const int vSpacing)
|
const int vSpacing)
|
||||||
: QLayout(parent), flowDirection(_flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing)
|
: QLayout(parent), flowDirection(flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing)
|
||||||
{
|
{
|
||||||
setContentsMargins(margin, margin, margin, margin);
|
setContentsMargins(margin, margin, margin, margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Destructor for FlowLayout, which cleans up all items in the layout.
|
|
||||||
*/
|
|
||||||
FlowLayout::~FlowLayout()
|
FlowLayout::~FlowLayout()
|
||||||
{
|
{
|
||||||
QLayoutItem *item;
|
QLayoutItem *item;
|
||||||
|
|
@ -42,499 +42,315 @@ FlowLayout::~FlowLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Indicates the layout's support for expansion in both horizontal and vertical directions.
|
* @brief Reports which axis the layout can expand along.
|
||||||
* @return The orientations (Qt::Horizontal | Qt::Vertical) this layout can expand to fill.
|
*
|
||||||
|
* A horizontally-flowing layout expands horizontally (and wraps vertically,
|
||||||
|
* but that is governed by heightForWidth, not by this flag).
|
||||||
|
* A vertically-flowing layout expands vertically.
|
||||||
*/
|
*/
|
||||||
Qt::Orientations FlowLayout::expandingDirections() const
|
Qt::Orientations FlowLayout::expandingDirections() const
|
||||||
{
|
{
|
||||||
return Qt::Horizontal | Qt::Vertical;
|
return (flowDirection == Qt::Horizontal) ? Qt::Horizontal : Qt::Vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Indicates that this layout's height depends on its width.
|
* @brief Height-for-width is only meaningful for horizontal (wrapping) flow.
|
||||||
* @return True, as the layout adjusts its height to fit the specified width.
|
|
||||||
*/
|
*/
|
||||||
bool FlowLayout::hasHeightForWidth() const
|
bool FlowLayout::hasHeightForWidth() const
|
||||||
{
|
{
|
||||||
return true;
|
return flowDirection == Qt::Horizontal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates the required height to display all items within the specified width.
|
* @brief Returns the height required to display all items within @p width.
|
||||||
* @param width The available width for arranging items.
|
*
|
||||||
* @return The total height needed to fit all items in rows constrained by the specified width.
|
* Only valid for horizontal flow; returns -1 otherwise so Qt ignores it.
|
||||||
|
* Spacing is counted once between adjacent items, never before the first
|
||||||
|
* or after the last.
|
||||||
*/
|
*/
|
||||||
int FlowLayout::heightForWidth(const int width) const
|
int FlowLayout::heightForWidth(const int width) const
|
||||||
{
|
{
|
||||||
if (flowDirection == Qt::Vertical) {
|
if (flowDirection != Qt::Horizontal)
|
||||||
int height = 0;
|
return -1;
|
||||||
int rowWidth = 0;
|
|
||||||
int rowHeight = 0;
|
|
||||||
|
|
||||||
for (const QLayoutItem *item : items) {
|
int totalHeight = 0;
|
||||||
if (item == nullptr || item->isEmpty()) {
|
int rowUsedWidth = 0;
|
||||||
continue;
|
int rowHeight = 0;
|
||||||
}
|
|
||||||
|
|
||||||
int itemWidth = item->sizeHint().width() + horizontalSpacing();
|
for (const QLayoutItem *item : items) {
|
||||||
if (rowWidth + itemWidth > width) {
|
if (!item || item->isEmpty()) {
|
||||||
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;
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Arranges layout items in rows within the specified rectangle bounds.
|
|
||||||
* @param rect The area within which to position layout items.
|
|
||||||
*/
|
|
||||||
void FlowLayout::setGeometry(const QRect &rect)
|
|
||||||
{
|
|
||||||
QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle.
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth);
|
|
||||||
|
|
||||||
if (QWidget *parentWidgetPtr = parentWidget()) {
|
|
||||||
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 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; // 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
const QSize itemSize = item->sizeHint();
|
||||||
int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
|
// Spacing is only inserted between items, not before the first.
|
||||||
|
const int spaceX = (rowUsedWidth > 0) ? horizontalSpacing() : 0;
|
||||||
|
|
||||||
// Check if the current item fits in the remaining width of the current row
|
if (rowUsedWidth > 0 && rowUsedWidth + spaceX + itemSize.width() > width) {
|
||||||
if (currentXPosition + itemWidth > availableWidth) {
|
// This item overflows the current row — commit the row and start a new one.
|
||||||
// If not, layout the current row and start a new row
|
totalHeight += rowHeight + verticalSpacing();
|
||||||
layoutSingleRow(rowItems, originX, currentYPosition);
|
rowUsedWidth = itemSize.width();
|
||||||
rowItems.clear(); // Reset the list for the new row
|
rowHeight = itemSize.height();
|
||||||
currentXPosition = originX; // Reset x-position to the row's start
|
} else {
|
||||||
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row
|
rowUsedWidth += spaceX + itemSize.width();
|
||||||
rowHeight = 0; // Reset row height for the new row
|
rowHeight = qMax(rowHeight, itemSize.height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalHeight + rowHeight; // Include the final (possibly only) row.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Places all children within @p rect.
|
||||||
|
*
|
||||||
|
* This is the only method that may move/resize children. It does NOT resize
|
||||||
|
* the parent widget; that would break Qt's layout protocol.
|
||||||
|
*/
|
||||||
|
void FlowLayout::setGeometry(const QRect &rect)
|
||||||
|
{
|
||||||
|
QLayout::setGeometry(rect);
|
||||||
|
|
||||||
|
if (flowDirection == Qt::Horizontal) {
|
||||||
|
layoutAllRows(rect.x(), rect.y(), rect.width());
|
||||||
|
} else {
|
||||||
|
layoutAllColumns(rect.x(), rect.y(), rect.height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize FlowLayout::sizeHint() const
|
||||||
|
{
|
||||||
|
return (flowDirection == Qt::Horizontal) ? calculateSizeHintHorizontal() : calculateSizeHintVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize FlowLayout::minimumSize() const
|
||||||
|
{
|
||||||
|
return (flowDirection == Qt::Horizontal) ? calculateMinimumSizeHorizontal() : calculateMinimumSizeVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Row layout (horizontal flow) ────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Places all items into wrapping rows within @p availableWidth.
|
||||||
|
* @return The y-coordinate of the bottom edge of the last row.
|
||||||
|
*/
|
||||||
|
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
|
||||||
|
{
|
||||||
|
QVector<QLayoutItem *> rowItems;
|
||||||
|
int rowUsedWidth = 0; // Width consumed by items already in the current row.
|
||||||
|
int currentY = originY;
|
||||||
|
int rowHeight = 0;
|
||||||
|
|
||||||
|
for (QLayoutItem *item : items) {
|
||||||
|
if (!item || item->isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSize itemSize = item->sizeHint();
|
||||||
|
// No leading space for the first item in a row.
|
||||||
|
const int spaceX = rowItems.isEmpty() ? 0 : horizontalSpacing();
|
||||||
|
|
||||||
|
if (!rowItems.isEmpty() && rowUsedWidth + spaceX + itemSize.width() > availableWidth) {
|
||||||
|
// Current item does not fit — flush the current row, begin a new one.
|
||||||
|
layoutSingleRow(rowItems, originX, currentY);
|
||||||
|
rowItems.clear();
|
||||||
|
currentY += rowHeight + verticalSpacing();
|
||||||
|
rowUsedWidth = 0;
|
||||||
|
rowHeight = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the item to the current row
|
// Add the item to the current row
|
||||||
rowItems.append(item);
|
rowItems.append(item);
|
||||||
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item
|
// `rowItems.size() > 1` is equivalent to "this is not the first item in the row"
|
||||||
currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item
|
// because we just appended above.
|
||||||
|
rowUsedWidth += (rowItems.size() > 1 ? horizontalSpacing() : 0) + itemSize.width();
|
||||||
|
rowHeight = qMax(rowHeight, itemSize.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout the final row if there are any remaining items
|
layoutSingleRow(rowItems, originX, currentY); // Flush the final row.
|
||||||
layoutSingleRow(rowItems, originX, currentYPosition);
|
return currentY + rowHeight;
|
||||||
|
|
||||||
// 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.
|
* @brief Sets the geometry for every item in @p rowItems, starting at (@p x, @p y).
|
||||||
* @param rowItems A list of items to be arranged in the row.
|
*
|
||||||
* @param x The starting x-coordinate for the row.
|
* Each item is placed at its sizeHint, clamped to its maximumSize.
|
||||||
* @param y The starting y-coordinate for the row.
|
|
||||||
*/
|
*/
|
||||||
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
|
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
|
||||||
{
|
{
|
||||||
for (QLayoutItem *item : rowItems) {
|
for (QLayoutItem *item : rowItems) {
|
||||||
if (item == nullptr || item->isEmpty()) {
|
if (!item || item->isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the maximum allowed size for the item
|
QWidget *widget = item->widget();
|
||||||
QSize itemMaxSize = item->widget()->maximumSize();
|
if (!widget) {
|
||||||
// Constrain the item's width and height to its size hint or maximum size
|
continue;
|
||||||
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
|
const QSize maxSize = widget->maximumSize();
|
||||||
|
const int itemWidth = qMin(item->sizeHint().width(), maxSize.width());
|
||||||
|
const int itemHeight = qMin(item->sizeHint().height(), maxSize.height());
|
||||||
|
|
||||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||||
// Move the x-position to the right, leaving space for horizontal spacing
|
// Move the x-position to the right, leaving space for horizontal spacing
|
||||||
x += itemWidth + horizontalSpacing();
|
x += itemWidth + horizontalSpacing();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Column layout (vertical flow) ───────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Lays out items into columns according to the available height, starting from a given origin.
|
* @brief Places all items into wrapping columns within @p availableHeight.
|
||||||
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
|
* @return The x-coordinate of the right edge of the last column.
|
||||||
* @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 FlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
|
int FlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
|
||||||
{
|
{
|
||||||
QVector<QLayoutItem *> colItems; // Holds items for the current column
|
QVector<QLayoutItem *> colItems;
|
||||||
int currentXPosition = originX; // Tracks the x-coordinate while placing items
|
int colUsedHeight = 0; // Height consumed by items already in the current column.
|
||||||
int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
|
int currentX = originX;
|
||||||
|
int colWidth = 0;
|
||||||
int colWidth = 0; // Tracks the maximum width of items in the current column
|
|
||||||
|
|
||||||
for (QLayoutItem *item : items) {
|
for (QLayoutItem *item : items) {
|
||||||
if (item == nullptr || item->isEmpty()) {
|
if (!item || item->isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
const QSize itemSize = item->sizeHint();
|
||||||
|
// No leading space for the first item in a column.
|
||||||
|
const int spaceY = colItems.isEmpty() ? 0 : verticalSpacing();
|
||||||
|
|
||||||
// Check if the current item fits in the remaining height of the current column
|
if (!colItems.isEmpty() && colUsedHeight + spaceY + itemSize.height() > availableHeight) {
|
||||||
if (currentYPosition + itemSize.height() > availableHeight) {
|
// Current item does not fit — flush the current column, begin a new one.
|
||||||
// If not, layout the current column and start a new column
|
layoutSingleColumn(colItems, currentX, originY);
|
||||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
colItems.clear();
|
||||||
colItems.clear(); // Reset the list for the new column
|
currentX += colWidth + horizontalSpacing();
|
||||||
currentYPosition = originY; // Reset y-position to the column's start
|
colUsedHeight = 0;
|
||||||
currentXPosition += colWidth; // Move x-position to the next column
|
colWidth = 0;
|
||||||
colWidth = 0; // Reset column width for the new column
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the item to the current column
|
|
||||||
colItems.append(item);
|
colItems.append(item);
|
||||||
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
|
colUsedHeight += (colItems.size() > 1 ? verticalSpacing() : 0) + itemSize.height();
|
||||||
currentYPosition += itemSize.height(); // Move y-position for the next item
|
colWidth = qMax(colWidth, itemSize.width());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout the final column if there are any remaining items
|
layoutSingleColumn(colItems, currentX, originY); // Flush the final column.
|
||||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
return currentX + colWidth;
|
||||||
|
|
||||||
// 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.
|
* @brief Sets the geometry for every item in @p colItems, starting at (@p x, @p y).
|
||||||
* @param colItems A list of items to be arranged in the column.
|
*
|
||||||
* @param x The starting x-coordinate for the column.
|
* Each item is placed at its sizeHint, clamped to its maximumSize.
|
||||||
* @param y The starting y-coordinate for the column.
|
|
||||||
*/
|
*/
|
||||||
void FlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
|
void FlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
|
||||||
{
|
{
|
||||||
for (QLayoutItem *item : colItems) {
|
for (QLayoutItem *item : colItems) {
|
||||||
if (item == nullptr) {
|
if (!item || item->isEmpty()) {
|
||||||
qCDebug(FlowLayoutLog) << "Item is null.";
|
qCDebug(FlowLayoutLog) << "Skipping null or empty item in column.";
|
||||||
continue;
|
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();
|
QWidget *widget = item->widget();
|
||||||
if (widget) {
|
if (!widget) {
|
||||||
qCDebug(FlowLayoutLog) << "Widget class:" << widget->metaObject()->className();
|
qCDebug(FlowLayoutLog) << "Item has no widget; skipping.";
|
||||||
qCDebug(FlowLayoutLog) << "Widget size hint:" << widget->sizeHint();
|
continue;
|
||||||
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
|
qCDebug(FlowLayoutLog) << "Widget:" << widget->metaObject()->className() << "sizeHint:" << widget->sizeHint()
|
||||||
QSize itemMaxSize = widget->maximumSize();
|
<< "maximumSize:" << widget->maximumSize() << "minimumSize:" << widget->minimumSize();
|
||||||
// Constrain the item's width and height to its size hint or maximum size
|
|
||||||
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
|
const QSize maxSize = widget->maximumSize();
|
||||||
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
|
const int itemWidth = qMin(item->sizeHint().width(), maxSize.width());
|
||||||
// Debugging: Print the computed geometry
|
const int itemHeight = qMin(item->sizeHint().height(), maxSize.height());
|
||||||
qCDebug(FlowLayoutLog) << "Computed geometry: x=" << x << ", y=" << y << ", width=" << itemWidth
|
|
||||||
<< ", height=" << itemHeight;
|
qCDebug(FlowLayoutLog) << "Placing at x=" << x << "y=" << y << "w=" << itemWidth << "h=" << itemHeight;
|
||||||
|
|
||||||
// Set the item's geometry based on the computed size and position
|
// Set the item's geometry based on the computed size and position
|
||||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||||
|
y += itemHeight + verticalSpacing();
|
||||||
// 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.
|
* @brief Preferred size for horizontal flow: all items in a single row (unconstrained).
|
||||||
* @return A QSize representing the ideal dimensions of the layout.
|
*
|
||||||
*/
|
* The actual displayed height is determined by heightForWidth() once Qt knows the
|
||||||
QSize FlowLayout::sizeHint() const
|
* real available width.
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
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
|
QSize FlowLayout::calculateSizeHintHorizontal() const
|
||||||
{
|
{
|
||||||
int maxWidth = 0; // Tracks the maximum width needed
|
int totalWidth = 0;
|
||||||
int totalHeight = 0; // Tracks the total height across all rows
|
int maxHeight = 0;
|
||||||
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) {
|
for (const QLayoutItem *item : items) {
|
||||||
if (!item || item->isEmpty()) {
|
if (!item || item->isEmpty()) {
|
||||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const QSize s = item->sizeHint();
|
||||||
QSize itemSize = item->sizeHint();
|
if (totalWidth > 0) {
|
||||||
int itemWidth = itemSize.width() + horizontalSpacing();
|
totalWidth += horizontalSpacing();
|
||||||
qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize << "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;
|
|
||||||
}
|
}
|
||||||
|
totalWidth += s.width();
|
||||||
currentWidth += itemWidth;
|
maxHeight = qMax(maxHeight, s.height());
|
||||||
rowHeight = qMax(rowHeight, itemSize.height());
|
|
||||||
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
|
|
||||||
}
|
}
|
||||||
|
return QSize(totalWidth, maxHeight);
|
||||||
// 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.
|
* @brief Minimum size for horizontal flow: the largest single item.
|
||||||
* @return A QSize representing the minimum required dimensions.
|
*
|
||||||
|
* This guarantees we can always display at least one item per row.
|
||||||
*/
|
*/
|
||||||
QSize FlowLayout::calculateMinimumSizeHorizontal() const
|
QSize FlowLayout::calculateMinimumSizeHorizontal() const
|
||||||
{
|
{
|
||||||
int maxWidth = 0; // Tracks the maximum width of a row
|
QSize size(0, 0);
|
||||||
int totalHeight = 0; // Tracks the total height across all rows
|
for (const QLayoutItem *item : items) {
|
||||||
int rowHeight = 0; // Tracks the height of the current row
|
if (!item || item->isEmpty()) {
|
||||||
int currentWidth = 0; // Tracks the current row's width
|
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size = size.expandedTo(item->minimumSize());
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
|
/**
|
||||||
|
* @brief Preferred size for vertical flow: all items in a single column (unconstrained).
|
||||||
qCDebug(FlowLayoutLog) << "Calculating horizontal minimum size. Available width:" << availableWidth;
|
*/
|
||||||
|
QSize FlowLayout::calculateSizeHintVertical() const
|
||||||
|
{
|
||||||
|
int maxWidth = 0;
|
||||||
|
int totalHeight = 0;
|
||||||
|
|
||||||
for (const QLayoutItem *item : items) {
|
for (const QLayoutItem *item : items) {
|
||||||
if (!item || item->isEmpty()) {
|
if (!item || item->isEmpty()) {
|
||||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const QSize s = item->sizeHint();
|
||||||
QSize itemMinSize = item->minimumSize();
|
if (totalHeight > 0) {
|
||||||
int itemWidth = itemMinSize.width() + horizontalSpacing();
|
totalHeight += verticalSpacing();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
totalHeight += s.height();
|
||||||
currentWidth += itemWidth;
|
maxWidth = qMax(maxWidth, s.width());
|
||||||
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);
|
return QSize(maxWidth, totalHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates the size hint for vertical flow direction.
|
* @brief Minimum size for vertical flow: the largest single item.
|
||||||
* @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
|
QSize FlowLayout::calculateMinimumSizeVertical() const
|
||||||
{
|
{
|
||||||
int totalWidth = 0; // Tracks the total width across all columns
|
QSize size(0, 0);
|
||||||
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) {
|
for (const QLayoutItem *item : items) {
|
||||||
if (!item || item->isEmpty()) {
|
if (!item || item->isEmpty()) {
|
||||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
size = size.expandedTo(item->minimumSize());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return size;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -543,7 +359,7 @@ QSize FlowLayout::calculateMinimumSizeVertical() const
|
||||||
*/
|
*/
|
||||||
void FlowLayout::addItem(QLayoutItem *item)
|
void FlowLayout::addItem(QLayoutItem *item)
|
||||||
{
|
{
|
||||||
if (item != nullptr) {
|
if (item) {
|
||||||
items.append(item);
|
items.append(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -551,11 +367,8 @@ void FlowLayout::addItem(QLayoutItem *item)
|
||||||
void FlowLayout::insertWidgetAtIndex(QWidget *toInsert, int index)
|
void FlowLayout::insertWidgetAtIndex(QWidget *toInsert, int index)
|
||||||
{
|
{
|
||||||
addChildWidget(toInsert);
|
addChildWidget(toInsert);
|
||||||
|
const int bounded = qBound(0, index, static_cast<int>(items.size()));
|
||||||
// We don't want to fail on an index that violates the bounds, so we just clamp it.
|
items.insert(bounded, new QWidgetItem(toInsert));
|
||||||
int boundedIndex = qBound(0, index, qMax(0, static_cast<int>(items.size())));
|
|
||||||
items.insert(boundedIndex, new QWidgetItem(toInsert));
|
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -613,52 +426,13 @@ int FlowLayout::verticalSpacing() const
|
||||||
*/
|
*/
|
||||||
int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const
|
int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const
|
||||||
{
|
{
|
||||||
QObject *parent = this->parent();
|
QObject *p = parent();
|
||||||
|
if (!p) {
|
||||||
if (!parent) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (p->isWidgetType()) {
|
||||||
if (parent->isWidgetType()) {
|
const auto *pw = static_cast<QWidget *>(p);
|
||||||
const auto *pw = dynamic_cast<QWidget *>(parent);
|
|
||||||
return pw->style()->pixelMetric(pm, nullptr, pw);
|
return pw->style()->pixelMetric(pm, nullptr, pw);
|
||||||
}
|
}
|
||||||
|
return static_cast<QLayout *>(p)->spacing();
|
||||||
return dynamic_cast<QLayout *>(parent)->spacing();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Gets the width of the parent scroll area, if any.
|
|
||||||
* @return The width of the scroll area's viewport, or 0 if not found.
|
|
||||||
*/
|
|
||||||
int FlowLayout::getParentScrollAreaWidth() const
|
|
||||||
{
|
|
||||||
QWidget *parent = parentWidget();
|
|
||||||
|
|
||||||
while (parent) {
|
|
||||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
|
||||||
return scrollArea->viewport()->width();
|
|
||||||
}
|
|
||||||
parent = parent->parentWidget();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Gets the height of the parent scroll area, if any.
|
|
||||||
* @return The height of the scroll area's viewport, or 0 if not found.
|
|
||||||
*/
|
|
||||||
int FlowLayout::getParentScrollAreaHeight() const
|
|
||||||
{
|
|
||||||
QWidget *parent = parentWidget();
|
|
||||||
|
|
||||||
while (parent) {
|
|
||||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
|
||||||
return scrollArea->viewport()->height();
|
|
||||||
}
|
|
||||||
parent = parent->parentWidget();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* @file flow_layout.h
|
* @file flow_layout.h
|
||||||
* @ingroup UI
|
* @ingroup UI
|
||||||
* @brief TODO: Document this.
|
* @brief A QLayout subclass that arranges child widgets in wrapping rows (horizontal flow)
|
||||||
|
* or wrapping columns (vertical flow).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FLOW_LAYOUT_H
|
#ifndef FLOW_LAYOUT_H
|
||||||
|
|
@ -10,8 +11,8 @@
|
||||||
#include <QLayout>
|
#include <QLayout>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
#include <QStyle>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <qstyle.h>
|
|
||||||
|
|
||||||
inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout", QtInfoMsg);
|
inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout", QtInfoMsg);
|
||||||
|
|
||||||
|
|
@ -19,42 +20,47 @@ class FlowLayout : public QLayout
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit FlowLayout(QWidget *parent = nullptr);
|
explicit FlowLayout(QWidget *parent = nullptr);
|
||||||
FlowLayout(QWidget *parent, Qt::Orientation _flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0);
|
FlowLayout(QWidget *parent, Qt::Orientation flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0);
|
||||||
~FlowLayout() override;
|
~FlowLayout() override;
|
||||||
|
|
||||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||||
|
|
||||||
[[nodiscard]] QSize calculateMinimumSizeHorizontal() const;
|
// QLayout interface
|
||||||
[[nodiscard]] QSize calculateSizeHintVertical() const;
|
|
||||||
[[nodiscard]] QSize calculateMinimumSizeVertical() const;
|
|
||||||
void addItem(QLayoutItem *item) override;
|
void addItem(QLayoutItem *item) override;
|
||||||
[[nodiscard]] int count() const override;
|
[[nodiscard]] int count() const override;
|
||||||
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
|
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
|
||||||
QLayoutItem *takeAt(int index) override;
|
QLayoutItem *takeAt(int index) override;
|
||||||
[[nodiscard]] int horizontalSpacing() const;
|
void setGeometry(const QRect &rect) override;
|
||||||
|
|
||||||
|
// Size negotiation
|
||||||
[[nodiscard]] Qt::Orientations expandingDirections() const override;
|
[[nodiscard]] Qt::Orientations expandingDirections() const override;
|
||||||
[[nodiscard]] bool hasHeightForWidth() const override;
|
[[nodiscard]] bool hasHeightForWidth() const override;
|
||||||
[[nodiscard]] int heightForWidth(int width) const override;
|
[[nodiscard]] int heightForWidth(int width) const override;
|
||||||
[[nodiscard]] int verticalSpacing() const;
|
[[nodiscard]] QSize sizeHint() const override;
|
||||||
[[nodiscard]] int doLayout(const QRect &rect, bool testOnly) const;
|
[[nodiscard]] QSize minimumSize() const override;
|
||||||
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
|
|
||||||
[[nodiscard]] int getParentScrollAreaWidth() const;
|
|
||||||
[[nodiscard]] int getParentScrollAreaHeight() const;
|
|
||||||
|
|
||||||
void setGeometry(const QRect &rect) override;
|
// Spacing helpers
|
||||||
|
[[nodiscard]] int horizontalSpacing() const;
|
||||||
|
[[nodiscard]] int verticalSpacing() const;
|
||||||
|
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
|
||||||
|
|
||||||
|
// Layout passes (virtual so subclasses can override placement logic)
|
||||||
virtual int layoutAllRows(int originX, int originY, int availableWidth);
|
virtual int layoutAllRows(int originX, int originY, int availableWidth);
|
||||||
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
|
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
|
||||||
int layoutAllColumns(int originX, int originY, int availableHeight);
|
int layoutAllColumns(int originX, int originY, int availableHeight);
|
||||||
void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
|
void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
|
||||||
[[nodiscard]] QSize sizeHint() const override;
|
|
||||||
[[nodiscard]] QSize minimumSize() const override;
|
|
||||||
[[nodiscard]] QSize calculateSizeHintHorizontal() const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QList<QLayoutItem *> items; // List to store layout items
|
// Size-hint helpers split by direction
|
||||||
|
[[nodiscard]] QSize calculateSizeHintHorizontal() const;
|
||||||
|
[[nodiscard]] QSize calculateMinimumSizeHorizontal() const;
|
||||||
|
[[nodiscard]] QSize calculateSizeHintVertical() const;
|
||||||
|
[[nodiscard]] QSize calculateMinimumSizeVertical() const;
|
||||||
|
|
||||||
|
QList<QLayoutItem *> items;
|
||||||
Qt::Orientation flowDirection;
|
Qt::Orientation flowDirection;
|
||||||
int horizontalMargin;
|
int horizontalMargin; ///< Horizontal spacing between items (-1 = use style default)
|
||||||
int verticalMargin;
|
int verticalMargin; ///< Vertical spacing between items (-1 = use style default)
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // FLOW_LAYOUT_H
|
#endif // FLOW_LAYOUT_H
|
||||||
|
|
@ -20,12 +20,10 @@
|
||||||
DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
|
DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
|
||||||
: AbstractAnalyticsPanelWidget(parent, analyzer)
|
: AbstractAnalyticsPanelWidget(parent, analyzer)
|
||||||
{
|
{
|
||||||
controls = new QWidget(this);
|
controls = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||||
controlLayout = new QHBoxLayout(controls);
|
|
||||||
controlLayout->setContentsMargins(11, 0, 11, 0);
|
|
||||||
|
|
||||||
labelPrefix = new QLabel(this);
|
labelPrefix = new QLabel(this);
|
||||||
controlLayout->addWidget(labelPrefix);
|
controls->addWidget(labelPrefix);
|
||||||
|
|
||||||
criteriaCombo = new QComboBox(this);
|
criteriaCombo = new QComboBox(this);
|
||||||
// Give these things item-data so we can translate the actual user-facing strings
|
// Give these things item-data so we can translate the actual user-facing strings
|
||||||
|
|
@ -33,33 +31,32 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatistics
|
||||||
criteriaCombo->addItem(QString(), "type");
|
criteriaCombo->addItem(QString(), "type");
|
||||||
criteriaCombo->addItem(QString(), "subtype");
|
criteriaCombo->addItem(QString(), "subtype");
|
||||||
criteriaCombo->addItem(QString(), "cmc");
|
criteriaCombo->addItem(QString(), "cmc");
|
||||||
controlLayout->addWidget(criteriaCombo);
|
controls->addWidget(criteriaCombo);
|
||||||
|
|
||||||
exactnessCombo = new QComboBox(this);
|
exactnessCombo = new QComboBox(this);
|
||||||
exactnessCombo->addItem(QString(), true); // At least
|
exactnessCombo->addItem(QString(), true); // At least
|
||||||
exactnessCombo->addItem(QString(), false); // Exactly
|
exactnessCombo->addItem(QString(), false); // Exactly
|
||||||
controlLayout->addWidget(exactnessCombo);
|
controls->addWidget(exactnessCombo);
|
||||||
|
|
||||||
quantitySpin = new QSpinBox(this);
|
quantitySpin = new QSpinBox(this);
|
||||||
quantitySpin->setRange(1, 60);
|
quantitySpin->setRange(1, 60);
|
||||||
controlLayout->addWidget(quantitySpin);
|
controls->addWidget(quantitySpin);
|
||||||
|
|
||||||
labelMiddle = new QLabel(this);
|
labelMiddle = new QLabel(this);
|
||||||
controlLayout->addWidget(labelMiddle);
|
controls->addWidget(labelMiddle);
|
||||||
|
|
||||||
drawnSpin = new QSpinBox(this);
|
drawnSpin = new QSpinBox(this);
|
||||||
drawnSpin->setRange(1, 60);
|
drawnSpin->setRange(1, 60);
|
||||||
drawnSpin->setValue(7);
|
drawnSpin->setValue(7);
|
||||||
controlLayout->addWidget(drawnSpin);
|
controls->addWidget(drawnSpin);
|
||||||
|
|
||||||
labelSuffix = new QLabel(this);
|
labelSuffix = new QLabel(this);
|
||||||
controlLayout->addWidget(labelSuffix);
|
controls->addWidget(labelSuffix);
|
||||||
|
|
||||||
labelPrefix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
labelPrefix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||||
labelMiddle->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
labelMiddle->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||||
labelSuffix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
labelSuffix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||||
|
|
||||||
controlLayout->addStretch(1);
|
|
||||||
layout->addWidget(controls);
|
layout->addWidget(controls);
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
#ifndef COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||||
#define COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
#define COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||||
|
|
||||||
|
#include "../../../../layouts/flow_layout.h"
|
||||||
|
#include "../../../general/layout_containers/flow_widget.h"
|
||||||
#include "../../abstract_analytics_panel_widget.h"
|
#include "../../abstract_analytics_panel_widget.h"
|
||||||
#include "../../deck_list_statistics_analyzer.h"
|
#include "../../deck_list_statistics_analyzer.h"
|
||||||
#include "draw_probability_config.h"
|
#include "draw_probability_config.h"
|
||||||
|
|
@ -31,8 +33,7 @@ private slots:
|
||||||
private:
|
private:
|
||||||
DrawProbabilityConfig config;
|
DrawProbabilityConfig config;
|
||||||
|
|
||||||
QWidget *controls;
|
FlowWidget *controls;
|
||||||
QHBoxLayout *controlLayout;
|
|
||||||
QLabel *labelPrefix;
|
QLabel *labelPrefix;
|
||||||
QLabel *labelMiddle;
|
QLabel *labelMiddle;
|
||||||
QLabel *labelSuffix;
|
QLabel *labelSuffix;
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,7 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
||||||
layout = new QVBoxLayout(this);
|
layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
// Controls
|
// Controls
|
||||||
controlContainer = new QWidget(this);
|
controlContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||||
controlLayout = new QHBoxLayout(controlContainer);
|
|
||||||
addButton = new QPushButton(this);
|
addButton = new QPushButton(this);
|
||||||
removeButton = new QPushButton(this);
|
removeButton = new QPushButton(this);
|
||||||
saveButton = new QPushButton(this);
|
saveButton = new QPushButton(this);
|
||||||
|
|
@ -32,11 +31,11 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
||||||
includeSideboardCheckBox = new QCheckBox(this);
|
includeSideboardCheckBox = new QCheckBox(this);
|
||||||
includeSideboardCheckBox->setChecked(false);
|
includeSideboardCheckBox->setChecked(false);
|
||||||
|
|
||||||
controlLayout->addWidget(addButton);
|
controlContainer->addWidget(addButton);
|
||||||
controlLayout->addWidget(removeButton);
|
controlContainer->addWidget(removeButton);
|
||||||
controlLayout->addWidget(saveButton);
|
controlContainer->addWidget(saveButton);
|
||||||
controlLayout->addWidget(loadButton);
|
controlContainer->addWidget(loadButton);
|
||||||
controlLayout->addWidget(includeSideboardCheckBox);
|
controlContainer->addWidget(includeSideboardCheckBox);
|
||||||
|
|
||||||
layout->addWidget(controlContainer);
|
layout->addWidget(controlContainer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#ifndef DECK_ANALYTICS_WIDGET_H
|
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||||
#define DECK_ANALYTICS_WIDGET_H
|
#define DECK_ANALYTICS_WIDGET_H
|
||||||
|
|
||||||
|
#include "../general/layout_containers/flow_widget.h"
|
||||||
#include "abstract_analytics_panel_widget.h"
|
#include "abstract_analytics_panel_widget.h"
|
||||||
#include "deck_list_statistics_analyzer.h"
|
#include "deck_list_statistics_analyzer.h"
|
||||||
#include "resizable_panel.h"
|
#include "resizable_panel.h"
|
||||||
|
|
@ -51,8 +52,7 @@ private:
|
||||||
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
||||||
|
|
||||||
QVBoxLayout *layout;
|
QVBoxLayout *layout;
|
||||||
QWidget *controlContainer;
|
FlowWidget *controlContainer;
|
||||||
QHBoxLayout *controlLayout;
|
|
||||||
|
|
||||||
QPushButton *addButton;
|
QPushButton *addButton;
|
||||||
QPushButton *removeButton;
|
QPushButton *removeButton;
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,52 @@
|
||||||
/**
|
/**
|
||||||
* @file flow_widget.cpp
|
* @file flow_widget.cpp
|
||||||
* @brief Implementation of the FlowWidget class for organizing widgets in a flow layout within a scrollable area.
|
* @brief Implementation of FlowWidget — a QWidget hosting a FlowLayout inside an
|
||||||
|
* optional QScrollArea.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "flow_widget.h"
|
#include "flow_widget.h"
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QSizePolicy>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <qscrollarea.h>
|
|
||||||
#include <qsizepolicy.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructs a FlowWidget with a scrollable layout.
|
* @brief Constructs a FlowWidget.
|
||||||
*
|
*
|
||||||
* @param parent The parent widget of this FlowWidget.
|
* When both scroll policies are Qt::ScrollBarAlwaysOff the scroll area is
|
||||||
* @param horizontalPolicy The horizontal scroll bar policy for the scroll area.
|
* omitted entirely and the container is placed directly in the main layout.
|
||||||
* @param verticalPolicy The vertical scroll bar policy for the scroll area.
|
*
|
||||||
|
* @param parent Parent widget.
|
||||||
|
* @param _flowDirection Qt::Horizontal for row-wrapping, Qt::Vertical for column-wrapping.
|
||||||
|
* @param horizontalPolicy Horizontal scroll-bar policy.
|
||||||
|
* @param verticalPolicy Vertical scroll-bar policy.
|
||||||
*/
|
*/
|
||||||
FlowWidget::FlowWidget(QWidget *parent,
|
FlowWidget::FlowWidget(QWidget *parent,
|
||||||
const Qt::Orientation _flowDirection,
|
const Qt::Orientation _flowDirection,
|
||||||
const Qt::ScrollBarPolicy horizontalPolicy,
|
const Qt::ScrollBarPolicy horizontalPolicy,
|
||||||
const Qt::ScrollBarPolicy verticalPolicy)
|
const Qt::ScrollBarPolicy verticalPolicy)
|
||||||
: QWidget(parent), flowDirection(_flowDirection)
|
: QWidget(parent), scrollArea(nullptr), flowDirection(_flowDirection)
|
||||||
|
|
||||||
{
|
{
|
||||||
// Main Widget and Layout
|
// Top-level size policy
|
||||||
if (_flowDirection == Qt::Horizontal) {
|
// Horizontal flow: expand horizontally, let height be determined by wrapping.
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
// Vertical flow: expand vertically, let width be determined by wrapping.
|
||||||
setMinimumWidth(0);
|
if (flowDirection == Qt::Horizontal) {
|
||||||
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
} else {
|
} else {
|
||||||
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
|
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||||
setMinimumHeight(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mainLayout = new QHBoxLayout(this);
|
mainLayout = new QHBoxLayout(this);
|
||||||
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
|
|
||||||
if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) {
|
const bool useScrollArea = (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 (optional)
|
||||||
|
if (useScrollArea) {
|
||||||
scrollArea = new QScrollArea(this);
|
scrollArea = new QScrollArea(this);
|
||||||
scrollArea->setWidgetResizable(true);
|
scrollArea->setWidgetResizable(true);
|
||||||
scrollArea->setMinimumSize(0, 0);
|
scrollArea->setMinimumSize(0, 0);
|
||||||
|
|
@ -48,39 +58,28 @@ FlowWidget::FlowWidget(QWidget *parent,
|
||||||
scrollArea = nullptr;
|
scrollArea = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flow Layout inside the scroll area
|
// Container widget (holds the FlowLayout)
|
||||||
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
|
container = new QWidget(useScrollArea ? static_cast<QWidget *>(scrollArea) : this);
|
||||||
container = new QWidget(this);
|
|
||||||
} else {
|
// The container should be willing to grow in both axes; its actual size is
|
||||||
container = new QWidget(scrollArea);
|
// governed by the FlowLayout's sizeHint / heightForWidth, not by a fixed policy.
|
||||||
}
|
container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||||
|
container->setMinimumSize(0, 0);
|
||||||
|
|
||||||
flowLayout = new FlowLayout(container, flowDirection);
|
flowLayout = new FlowLayout(container, flowDirection);
|
||||||
|
|
||||||
container->setLayout(flowLayout);
|
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 (useScrollArea) {
|
||||||
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
|
|
||||||
mainLayout->addWidget(container);
|
|
||||||
} else {
|
|
||||||
scrollArea->setWidget(container);
|
scrollArea->setWidget(container);
|
||||||
mainLayout->addWidget(scrollArea);
|
mainLayout->addWidget(scrollArea);
|
||||||
|
} else {
|
||||||
|
mainLayout->addWidget(container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Adds a widget to the flow layout within the FlowWidget.
|
* @brief Adds a widget to the flow layout within the FlowWidget.
|
||||||
*
|
*
|
||||||
* Adjusts the widget's size policy based on the scroll bar policies.
|
|
||||||
*
|
|
||||||
* @param widget_to_add The widget to add to the flow layout.
|
* @param widget_to_add The widget to add to the flow layout.
|
||||||
*/
|
*/
|
||||||
void FlowWidget::addWidget(QWidget *widget_to_add) const
|
void FlowWidget::addWidget(QWidget *widget_to_add) const
|
||||||
|
|
@ -100,77 +99,67 @@ void FlowWidget::removeWidget(QWidget *widgetToRemove) const
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Clears all widgets from the flow layout.
|
* @brief Removes all widgets from the flow layout and deletes them.
|
||||||
*
|
*
|
||||||
* Deletes each widget and layout item, and recreates the flow layout if it was removed.
|
* If the layout pointer has somehow been lost it is recreated before returning.
|
||||||
*/
|
*/
|
||||||
void FlowWidget::clearLayout()
|
void FlowWidget::clearLayout()
|
||||||
{
|
{
|
||||||
if (flowLayout != nullptr) {
|
if (flowLayout) {
|
||||||
QLayoutItem *item;
|
QLayoutItem *item;
|
||||||
while ((item = flowLayout->takeAt(0)) != nullptr) {
|
while ((item = flowLayout->takeAt(0))) {
|
||||||
item->widget()->deleteLater(); // Delete the widget
|
if (item->widget())
|
||||||
delete item; // Delete the layout item
|
item->widget()->deleteLater();
|
||||||
|
delete item;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Defensive fallback: recreate the layout if it was deleted externally.
|
||||||
flowLayout = new FlowLayout(container, flowDirection);
|
flowLayout = new FlowLayout(container, flowDirection);
|
||||||
container->setLayout(flowLayout);
|
container->setLayout(flowLayout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Handles resize events for the FlowWidget.
|
* @brief Marks the flow layout as dirty so Qt recomputes item positions.
|
||||||
*
|
*
|
||||||
* Triggers layout recalculation and adjusts the scroll area content size.
|
* We do NOT call adjustSize() or activate() here:
|
||||||
*
|
* - adjustSize() would freeze geometry by calling setFixedSize internally.
|
||||||
* @param event The resize event containing the new size information.
|
* - activate() called inside a resize event can cause synchronous re-entrancy.
|
||||||
|
* Qt automatically calls setGeometry on the layout after a resize, so simply
|
||||||
|
* invalidating is sufficient.
|
||||||
*/
|
*/
|
||||||
void FlowWidget::resizeEvent(QResizeEvent *event)
|
void FlowWidget::resizeEvent(QResizeEvent *event)
|
||||||
{
|
{
|
||||||
QWidget::resizeEvent(event);
|
QWidget::resizeEvent(event);
|
||||||
|
qCDebug(FlowWidgetSizeLog) << "resizeEvent:" << event->size();
|
||||||
|
|
||||||
qCDebug(FlowWidgetSizeLog) << event->size();
|
if (flowLayout) {
|
||||||
|
flowLayout->invalidate();
|
||||||
// Trigger the layout to recalculate
|
|
||||||
if (flowLayout != nullptr) {
|
|
||||||
flowLayout->invalidate(); // Marks the layout as dirty and requires recalculation
|
|
||||||
flowLayout->activate(); // Recalculate the layout based on the new size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the scroll area and its content adjust correctly
|
|
||||||
if (scrollArea != nullptr && scrollArea->widget() != nullptr) {
|
|
||||||
qCDebug(FlowWidgetSizeLog) << "Got a scrollarea: " << scrollArea->widget()->size();
|
|
||||||
scrollArea->widget()->adjustSize();
|
|
||||||
} else {
|
|
||||||
container->adjustSize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sets the minimum size for all widgets inside the FlowWidget to the maximum sizeHint of all of them.
|
* @brief Sets every child widget's minimum size to the largest sizeHint in the layout.
|
||||||
|
*
|
||||||
|
* Useful for toolbars or button bars where all items should be the same size.
|
||||||
*/
|
*/
|
||||||
void FlowWidget::setMinimumSizeToMaxSizeHint()
|
void FlowWidget::setMinimumSizeToMaxSizeHint()
|
||||||
{
|
{
|
||||||
QSize maxSize(0, 0); // Initialize to a zero size
|
QSize maxSize(0, 0);
|
||||||
|
|
||||||
// Iterate over all widgets in the flow layout to find the maximum sizeHint
|
// Iterate over all widgets in the flow layout to find the maximum sizeHint
|
||||||
for (int i = 0; i < flowLayout->count(); ++i) {
|
for (int i = 0; i < flowLayout->count(); ++i) {
|
||||||
if (QLayoutItem *item = flowLayout->itemAt(i)) {
|
QLayoutItem *item = flowLayout->itemAt(i);
|
||||||
if (QWidget *widget = item->widget()) {
|
if (item && item->widget()) {
|
||||||
// Update the max size based on the sizeHint of each widget
|
maxSize = maxSize.expandedTo(item->widget()->sizeHint());
|
||||||
QSize widgetSizeHint = widget->sizeHint();
|
|
||||||
maxSize.setWidth(qMax(maxSize.width(), widgetSizeHint.width()));
|
|
||||||
maxSize.setHeight(qMax(maxSize.height(), widgetSizeHint.height()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the minimum size for all widgets to the max sizeHint
|
// Set the minimum size for all widgets to the max sizeHint
|
||||||
for (int i = 0; i < flowLayout->count(); ++i) {
|
for (int i = 0; i < flowLayout->count(); ++i) {
|
||||||
if (QLayoutItem *item = flowLayout->itemAt(i)) {
|
QLayoutItem *item = flowLayout->itemAt(i);
|
||||||
if (QWidget *widget = item->widget()) {
|
if (item && item->widget()) {
|
||||||
widget->setMinimumSize(maxSize);
|
item->widget()->setMinimumSize(maxSize);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
/**
|
/**
|
||||||
* @file flow_widget.h
|
* @file flow_widget.h
|
||||||
* @ingroup UI
|
* @ingroup UI
|
||||||
* @brief TODO: Document this.
|
* @brief A QWidget that wraps a FlowLayout inside an optional QScrollArea.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FLOW_WIDGET_H
|
#ifndef FLOW_WIDGET_H
|
||||||
#define FLOW_WIDGET_H
|
#define FLOW_WIDGET_H
|
||||||
|
|
||||||
#include "../../../layouts/flow_layout.h"
|
#include "../../../layouts/flow_layout.h"
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
#include <QScrollArea>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <qscrollarea.h>
|
|
||||||
|
|
||||||
inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget", QtInfoMsg);
|
inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget", QtInfoMsg);
|
||||||
inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size", QtInfoMsg);
|
inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size", QtInfoMsg);
|
||||||
|
|
@ -25,14 +26,16 @@ public:
|
||||||
Qt::Orientation orientation,
|
Qt::Orientation orientation,
|
||||||
Qt::ScrollBarPolicy horizontalPolicy,
|
Qt::ScrollBarPolicy horizontalPolicy,
|
||||||
Qt::ScrollBarPolicy verticalPolicy);
|
Qt::ScrollBarPolicy verticalPolicy);
|
||||||
|
|
||||||
void addWidget(QWidget *widget_to_add) const;
|
void addWidget(QWidget *widget_to_add) const;
|
||||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||||
void removeWidget(QWidget *widgetToRemove) const;
|
void removeWidget(QWidget *widgetToRemove) const;
|
||||||
void clearLayout();
|
void clearLayout();
|
||||||
|
|
||||||
[[nodiscard]] int count() const;
|
[[nodiscard]] int count() const;
|
||||||
[[nodiscard]] QLayoutItem *itemAt(int index) const;
|
[[nodiscard]] QLayoutItem *itemAt(int index) const;
|
||||||
|
|
||||||
QScrollArea *scrollArea;
|
QScrollArea *scrollArea; ///< Null when both scroll policies are AlwaysOff.
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setMinimumSizeToMaxSizeHint();
|
void setMinimumSizeToMaxSizeHint();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue