Files
ts-qt/modern-qt/layout/flow.cc

157 lines
5.3 KiB
C++

#include "flow.hh"
#include <qstyle.h>
using namespace creeper::flow::internal;
struct Flow::Impl {
QList<Item*> items;
int row_spacing = 10;
int col_spacing = 10;
int row_limit = std::numeric_limits<int>::max();
Flow& self;
explicit Impl(Flow& self) noexcept
: self { self } { }
auto calculate_spacing(QStyle::PixelMetric pm) const -> int {
const auto parent = self.parent();
if (!parent) {
return -1;
} else if (parent->isWidgetType()) {
const auto pw = static_cast<QWidget*>(parent);
return pw->style()->pixelMetric(pm, nullptr, pw);
} else {
return static_cast<QLayout*>(parent)->spacing();
}
}
auto horizontal_spacing() const -> int {
if (row_spacing >= 0) {
return row_spacing;
} else {
return calculate_spacing(QStyle::PM_LayoutHorizontalSpacing);
}
}
auto vertical_spacing() const -> int {
if (col_spacing >= 0) {
return col_spacing;
} else {
return calculate_spacing(QStyle::PM_LayoutVerticalSpacing);
}
}
auto update_items_geometry(Item* item, const QPoint& point) const {
// TODO: 显然,这个接口未来可以拓展成带动画的位姿更新
item->setGeometry({ point, item->sizeHint() });
}
// Flow 理应是一个常用的布局,但 Qt 中没有对应的实现,
// https://doc.qt.io/archives/qt-5.15/qtwidgets-layouts-flowlayout-example.html
auto calculate_layout(const QRect& rect, bool apply) const -> int {
int left, top, right, bottom;
self.getContentsMargins(&left, &top, &right, &bottom);
const auto effective_rect = rect.adjusted(+left, +top, -right, -bottom);
auto current_x = effective_rect.x();
auto current_y = effective_rect.y();
auto line_height = int { 0 };
auto line_length = int { 0 };
for (auto item : std::as_const(items)) {
const auto widget = item->widget();
const auto spacing = [widget](Qt::Orientation o) {
return widget->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, o);
};
auto space_x = row_spacing;
if (space_x == -1) space_x = spacing(Qt::Horizontal);
auto space_y = col_spacing;
if (space_y == -1) space_y = spacing(Qt::Vertical);
auto next_x = current_x + item->sizeHint().width() + space_x;
const auto area_flag = next_x - space_x > effective_rect.right();
const auto size_flag = line_length > row_limit;
if ((area_flag || size_flag) && line_height > 0) {
current_x = effective_rect.x();
current_y = current_y + line_height + space_y;
next_x = current_x + item->sizeHint().width() + space_x;
line_height = 0;
line_length = 0;
}
if (apply) {
const auto point = QPoint { current_x, current_y };
update_items_geometry(item, point);
}
current_x = next_x;
line_height = std::max(line_height, item->sizeHint().height());
line_length = line_length + 1;
}
return current_y + line_height - rect.y() + bottom;
}
};
Flow::Flow()
: pimpl { std::make_unique<Impl>(*this) } { }
Flow::~Flow() {
while (auto item = Flow::takeAt(0))
delete item;
}
auto Flow::addItem(Item* item) -> void { pimpl->items.append(item); }
auto Flow::setGeometry(const QRect& rect) -> void {
QLayout::setGeometry(rect);
pimpl->calculate_layout(rect, true);
}
auto Flow::takeAt(int index) -> Item* {
auto& items = pimpl->items;
return (index < 0 || index > items.size() - 1) ? nullptr : items.takeAt(index);
}
auto Flow::expandingDirections() const -> Qt::Orientations { return {}; }
auto Flow::hasHeightForWidth() const -> bool { return true; }
auto Flow::heightForWidth(int width) const -> int {
return pimpl->calculate_layout({ 0, 0, width, 0 }, false);
}
auto Flow::itemAt(int index) const -> Item* { return pimpl->items.value(index); }
auto Flow::count() const -> int { return pimpl->items.size(); }
auto Flow::minimumSize() const -> QSize {
auto result = QSize {};
for (const auto item : std::as_const(pimpl->items))
result = result.expandedTo(item->minimumSize());
const auto margins = contentsMargins();
result += QSize {
margins.left() + margins.right(),
margins.top() + margins.bottom(),
};
return result;
}
auto Flow::sizeHint() const -> QSize { return Flow::minimumSize(); }
auto Flow::set_row_spacing(int spacing) noexcept -> void { pimpl->row_spacing = spacing; }
auto Flow::row_spacing() const noexcept -> int { return pimpl->row_spacing; }
auto Flow::set_col_spacing(int spacing) noexcept -> void { pimpl->col_spacing = spacing; }
auto Flow::col_spacing() const noexcept -> int { return pimpl->col_spacing; }
auto Flow::set_row_limit(int limit) noexcept -> void { pimpl->row_limit = limit; }
auto Flow::row_limit() const noexcept -> int { return pimpl->row_limit; }