From 3cf9b767ac2ed07ad81d735ead9f96f6de910191 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 23 Apr 2024 23:53:03 +0200 Subject: [PATCH] Start: add dynamic layout FlowLayout --- src/Mod/Start/Gui/CMakeLists.txt | 2 + src/Mod/Start/Gui/FlowLayout.cpp | 193 +++++++++++++++++++++++++++++++ src/Mod/Start/Gui/FlowLayout.h | 75 ++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 src/Mod/Start/Gui/FlowLayout.cpp create mode 100644 src/Mod/Start/Gui/FlowLayout.h diff --git a/src/Mod/Start/Gui/CMakeLists.txt b/src/Mod/Start/Gui/CMakeLists.txt index 9f8141ad35..5d6559e9fd 100644 --- a/src/Mod/Start/Gui/CMakeLists.txt +++ b/src/Mod/Start/Gui/CMakeLists.txt @@ -53,6 +53,8 @@ SET(StartGui_SRCS FileCardDelegate.h FileCardView.cpp FileCardView.h + FlowLayout.cpp + FlowLayout.h Manipulator.cpp Manipulator.h PreCompiled.cpp diff --git a/src/Mod/Start/Gui/FlowLayout.cpp b/src/Mod/Start/Gui/FlowLayout.cpp new file mode 100644 index 0000000000..0a92b07922 --- /dev/null +++ b/src/Mod/Start/Gui/FlowLayout.cpp @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2024 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" +#include +#include "FlowLayout.h" + +using namespace StartGui; + +FlowLayout::FlowLayout(QWidget* parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent) + , hSpace {hSpacing} + , vSpace {vSpacing} +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::~FlowLayout() +{ + QLayoutItem* item {}; + while ((item = takeAt(0))) { + delete item; + } +} + +void FlowLayout::addItem(QLayoutItem* item) +{ + itemList.append(item); +} + +int FlowLayout::horizontalSpacing() const +{ + if (hSpace >= 0) { + return hSpace; + } + + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); +} + +int FlowLayout::verticalSpacing() const +{ + if (vSpace >= 0) { + return vSpace; + } + + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); +} + +int FlowLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem* FlowLayout::itemAt(int index) const +{ + if (index >= 0 && index < itemList.size()) { + return itemList[index]; + } + + return nullptr; +} + +QLayoutItem* FlowLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) { + return itemList.takeAt(index); + } + + return nullptr; +} + +Qt::Orientations FlowLayout::expandingDirections() const +{ + return {}; +} + +bool FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int FlowLayout::heightForWidth(int width) const +{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} + +void FlowLayout::setGeometry(const QRect& rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const +{ + QSize size; + for (auto item : qAsConst(itemList)) { + size = size.expandedTo(item->minimumSize()); + } + + QMargins margins = contentsMargins(); + size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom()); + return size; +} + +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + QObject* par = parent(); + if (!par) { + return -1; + } + + if (par->isWidgetType()) { + auto widget = qobject_cast(par); + return widget->style()->pixelMetric(pm, nullptr, widget); + } + + return static_cast(par)->spacing(); +} + +int FlowLayout::doLayout(const QRect& rect, bool testOnly) const +{ + int left {}; + int top {}; + int right {}; + int bottom {}; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + + for (auto item : qAsConst(itemList)) { + QWidget* wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) { + spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, + QSizePolicy::PushButton, + Qt::Horizontal); + } + + int spaceY = verticalSpacing(); + if (spaceY == -1) { + spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, + QSizePolicy::PushButton, + Qt::Vertical); + } + + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) { + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + } + + x = nextX; + lineHeight = std::max(lineHeight, item->sizeHint().height()); + } + + return y + lineHeight - rect.y() + bottom; +} + +#include "moc_FlowLayout.cpp" diff --git a/src/Mod/Start/Gui/FlowLayout.h b/src/Mod/Start/Gui/FlowLayout.h new file mode 100644 index 0000000000..2518df809d --- /dev/null +++ b/src/Mod/Start/Gui/FlowLayout.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2024 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef FREECAD_FLOWLAYOUT_H +#define FREECAD_FLOWLAYOUT_H + +#include +#include +#include + +namespace StartGui +{ + +/*! + * \brief The FlowLayout class + * Based on https://forum.qt.io/topic/109408/is-there-a-qt-layout-grid-that-can-dynamically- + * change-row-and-column-counts-to-best-fit-the-space + */ +class FlowLayout: public QLayout +{ + Q_OBJECT + +public: + explicit FlowLayout(QWidget* parent = nullptr, + int margin = -1, + int hSpacing = -1, + int vSpacing = -1); + ~FlowLayout() override; + + void addItem(QLayoutItem* item) override; + int count() const override; + QLayoutItem* itemAt(int index) const override; + QLayoutItem* takeAt(int index) override; + Qt::Orientations expandingDirections() const override; + bool hasHeightForWidth() const override; + int heightForWidth(int width) const override; + void setGeometry(const QRect& rect) override; + QSize sizeHint() const override; + QSize minimumSize() const override; + +private: + int horizontalSpacing() const; + int verticalSpacing() const; + int smartSpacing(QStyle::PixelMetric pm) const; + int doLayout(const QRect& rect, bool testOnly) const; + +private: + QList itemList; + int hSpace = -1; + int vSpace = -1; +}; + +} // namespace StartGui + +#endif // FREECAD_FLOWLAYOUT_H