Extract Mosaic::Layout::AbstractMosaicLayout.

This commit is contained in:
John Preston 2021-07-28 17:00:46 +03:00
parent 4a86b172d4
commit 868c494299
6 changed files with 504 additions and 361 deletions

View File

@ -343,7 +343,7 @@ void GifsListWidget::paintInlineItems(Painter &p, QRect clip) {
using namespace InlineBots::Layout;
PaintContext context(crl::now(), false, gifPaused, false);
auto paintItem = [&](const not_null<ItemBase*> item, QPoint point) {
auto paintItem = [&](not_null<const ItemBase*> item, QPoint point) {
p.translate(point.x(), point.y());
item->paint(
p,
@ -624,7 +624,7 @@ void GifsListWidget::deleteUnusedInlineLayouts() {
}
void GifsListWidget::preloadImages() {
_mosaic.forEach([](const not_null<LayoutItem*> item) {
_mosaic.forEach([](not_null<const LayoutItem*> item) {
item->preload();
});
}
@ -679,7 +679,7 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
int GifsListWidget::validateExistingInlineRows(const InlineResults &results) {
const auto until = _mosaic.validateExistingRows([&](
const not_null<LayoutItem*> item,
not_null<const LayoutItem*> item,
int untilIndex) {
return item->getResult() != results[untilIndex].get();
}, results.size());

View File

@ -445,7 +445,7 @@ void ListWidget::Section::paint(
localContext.isAfterDate = (header > 0);
if (!_mosaic.empty()) {
auto paintItem = [&](const not_null<BaseLayout*> item, QPoint point) {
auto paintItem = [&](not_null<BaseLayout*> item, QPoint point) {
p.translate(point.x(), point.y());
item->paint(
p,

View File

@ -192,7 +192,7 @@ void Inner::paintInlineItems(Painter &p, const QRect &r) {
context.pathGradient = _pathGradient.get();
context.pathGradient->startFrame(0, width(), width() / 2);
auto paintItem = [&](const not_null<ItemBase*> item, QPoint point) {
auto paintItem = [&](not_null<const ItemBase*> item, QPoint point) {
p.translate(point.x(), point.y());
item->paint(
p,
@ -384,7 +384,7 @@ void Inner::deleteUnusedInlineLayouts() {
}
void Inner::preloadImages() {
_mosaic.forEach([](const not_null<ItemBase*> item) {
_mosaic.forEach([](not_null<const ItemBase*> item) {
item->preload();
});
}
@ -488,7 +488,7 @@ int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntr
int Inner::validateExistingInlineRows(const Results &results) {
const auto until = _mosaic.validateExistingRows([&](
const not_null<ItemBase*> item,
not_null<const ItemBase*> item,
int untilIndex) {
return item->getResult() != results[untilIndex].get();
}, results.size());

View File

@ -0,0 +1,391 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "layout/layout_mosaic.h"
namespace Mosaic::Layout {
AbstractMosaicLayout::AbstractMosaicLayout(int bigWidth)
: _bigWidth(bigWidth) {
}
int AbstractMosaicLayout::rowHeightAt(int row) const {
Expects(row >= 0 && row < _rows.size());
return _rows[row].height;
}
int AbstractMosaicLayout::countDesiredHeight(int newWidth) {
auto result = 0;
for (auto &row : _rows) {
layoutRow(row, newWidth ? newWidth : _width);
result += row.height;
}
return result;
}
FoundItem AbstractMosaicLayout::findByPoint(const QPoint &globalPoint) const {
auto sx = globalPoint.x() - _offset.x();
auto sy = globalPoint.y() - _offset.y();
auto row = -1;
auto col = -1;
auto sel = -1;
bool exact = true;
if (sy >= 0) {
row = 0;
for (auto rows = rowsCount(); row < rows; ++row) {
const auto rowHeight = _rows[row].height;
if (sy < rowHeight) {
break;
}
sy -= rowHeight;
}
} else {
row = 0;
exact = false;
}
if (row >= rowsCount()) {
row = rowsCount() - 1;
exact = false;
}
if (sx < 0) {
sx = 0;
exact = false;
}
if (sx >= 0 && row >= 0 && row < rowsCount()) {
const auto columnsCount = _rows[row].items.size();
col = 0;
for (int cols = columnsCount; col < cols; ++col) {
const auto item = itemAt(row, col);
const auto width = item->width();
if (sx < width) {
break;
}
sx -= width;
sx -= _rightSkip;
}
if (col >= columnsCount) {
col = columnsCount - 1;
exact = false;
}
sel = ::Layout::PositionToIndex(row, + col);
} else {
row = col = -1;
}
return { sel, exact, QPoint(sx, sy) };
}
QRect AbstractMosaicLayout::findRect(int index) const {
const auto clip = QRect(0, 0, _width, 100);
const auto fromX = style::RightToLeft()
? (_width - clip.x() - clip.width())
: clip.x();
const auto toX = style::RightToLeft()
? (_width - clip.x())
: (clip.x() + clip.width());
const auto rows = _rows.size();
auto top = 0;
for (auto row = 0; row != rows; ++row) {
auto &inlineRow = _rows[row];
// if ((top + inlineRow.height) > clip.top()) {
auto left = 0;
if (row == (rows - 1)) {
// context.lastRow = true;
}
for (const auto &item : inlineRow.items) {
if (left >= toX) {
break;
}
const auto w = item->width();
if ((left + w) > fromX) {
if (item->position() == index) {
return QRect(
left + _offset.x(),
top + _offset.y(),
item->width(),
item->height());
}
}
left += w;
left += _rightSkip;
}
// }
top += inlineRow.height;
}
return QRect();
}
void AbstractMosaicLayout::addItems(
gsl::span<const not_null<AbstractLayoutItem*>> items) {
_rows.reserve(items.size());
auto row = Row();
row.items.reserve(kInlineItemsMaxPerRow);
auto sumWidth = 0;
for (const auto &item : items) {
addItem(item, row, sumWidth);
}
rowFinalize(row, sumWidth, true);
}
void AbstractMosaicLayout::setRightSkip(int rightSkip) {
_rightSkip = rightSkip;
}
void AbstractMosaicLayout::setOffset(int left, int top) {
_offset = { left, top };
}
void AbstractMosaicLayout::setFullWidth(int w) {
_width = w;
}
bool AbstractMosaicLayout::empty() const {
return _rows.empty();
}
int AbstractMosaicLayout::rowsCount() const {
return _rows.size();
}
not_null<AbstractLayoutItem*> AbstractMosaicLayout::itemAt(
int row,
int column) const {
Expects((row >= 0)
&& (row < _rows.size())
&& (column >= 0)
&& (column < _rows[row].items.size()));
return _rows[row].items[column];
}
not_null<AbstractLayoutItem*> AbstractMosaicLayout::itemAt(int index) const {
const auto &[row, column] = ::Layout::IndexToPosition(index);
return itemAt(row, column);
}
AbstractLayoutItem *AbstractMosaicLayout::maybeItemAt(
int row,
int column) const {
if ((row >= 0)
&& (row < _rows.size())
&& (column >= 0)
&& (column < _rows[row].items.size())) {
return _rows[row].items[column];
}
return nullptr;
}
AbstractLayoutItem *AbstractMosaicLayout::maybeItemAt(int index) const {
const auto &[row, column] = ::Layout::IndexToPosition(index);
return maybeItemAt(row, column);
}
void AbstractMosaicLayout::clearRows(bool resultsDeleted) {
if (!resultsDeleted) {
for (const auto &row : _rows) {
for (const auto &item : row.items) {
item->setPosition(-1);
}
}
}
_rows.clear();
}
void AbstractMosaicLayout::forEach(
Fn<void(not_null<const AbstractLayoutItem*>)> callback) {
for (const auto &row : _rows) {
for (const auto &item : row.items) {
callback(item);
}
}
}
void AbstractMosaicLayout::paint(
Fn<void(not_null<AbstractLayoutItem*>, QPoint)> paintItem,
const QRect &clip) const {
auto top = _offset.y();
const auto fromX = style::RightToLeft()
? (_width - clip.x() - clip.width())
: clip.x();
const auto toX = style::RightToLeft()
? (_width - clip.x())
: (clip.x() + clip.width());
const auto rows = _rows.size();
for (auto row = 0; row != rows; ++row) {
if (top >= clip.top() + clip.height()) {
break;
}
auto &inlineRow = _rows[row];
if ((top + inlineRow.height) > clip.top()) {
auto left = _offset.x();
if (row == (rows - 1)) {
// context.lastRow = true;
}
for (const auto &item : inlineRow.items) {
if (left >= toX) {
break;
}
const auto w = item->width();
if ((left + w) > fromX) {
paintItem(item, QPoint(left, top));
}
left += w;
left += _rightSkip;
}
}
top += inlineRow.height;
}
}
int AbstractMosaicLayout::validateExistingRows(
Fn<bool(not_null<const AbstractLayoutItem*>, int)> checkItem,
int count) {
auto until = 0;
auto untilRow = 0;
auto untilCol = 0;
while (until < count) {
auto &rowItems = _rows[untilRow].items;
if ((untilRow >= _rows.size())
|| checkItem(rowItems[untilCol], until)) {
break;
}
++until;
if (++untilCol == rowItems.size()) {
++untilRow;
untilCol = 0;
}
}
if (until == count) { // All items are layed out.
if (untilRow == _rows.size()) { // Nothing changed.
return until;
}
{
const auto rows = _rows.size();
auto skip = untilCol;
for (auto i = untilRow; i < rows; ++i) {
for (const auto &item : _rows[i].items) {
if (skip) {
--skip;
} else {
item->setPosition(-1);
}
}
}
}
if (!untilCol) { // All good rows are filled.
_rows.resize(untilRow);
return until;
}
_rows.resize(untilRow + 1);
_rows[untilRow].items.resize(untilCol);
_rows[untilRow].maxWidth = ranges::accumulate(
_rows[untilRow].items,
0,
[](int w, auto &row) { return w + row->maxWidth(); });
layoutRow(_rows[untilRow], _width);
return until;
}
if (untilRow && !untilCol) { // Remove last row, maybe it is not full.
--untilRow;
untilCol = _rows[untilRow].items.size();
}
until -= untilCol;
for (auto i = untilRow; i < _rows.size(); ++i) {
for (const auto &item : _rows[i].items) {
item->setPosition(-1);
}
}
_rows.resize(untilRow);
return until;
}
void AbstractMosaicLayout::addItem(
not_null<AbstractLayoutItem*> item,
Row &row,
int &sumWidth) {
// item->preload();
using namespace ::Layout;
item->setPosition(PositionToIndex(_rows.size(), row.items.size()));
if (rowFinalize(row, sumWidth, false)) {
item->setPosition(PositionToIndex(_rows.size(), 0));
}
sumWidth += item->maxWidth();
if (!row.items.empty() && _rightSkip) {
sumWidth += _rightSkip;
}
row.items.push_back(item);
}
bool AbstractMosaicLayout::rowFinalize(Row &row, int &sumWidth, bool force) {
if (row.items.empty()) {
return false;
}
const auto full = (row.items.size() >= kInlineItemsMaxPerRow);
// Currently use the same GIFs layout for all widget sizes.
const auto big = (sumWidth >= _bigWidth);
if (full || big || force) {
row.maxWidth = (full || big) ? sumWidth : 0;
layoutRow(row, _width);
_rows.push_back(std::move(row));
row = Row();
row.items.reserve(kInlineItemsMaxPerRow);
sumWidth = 0;
return true;
}
return false;
}
void AbstractMosaicLayout::layoutRow(Row &row, int fullWidth) {
const auto count = int(row.items.size());
Assert(count <= kInlineItemsMaxPerRow);
// Enumerate items in the order of growing maxWidth()
// for that sort item indices by maxWidth().
int indices[kInlineItemsMaxPerRow];
for (auto i = 0; i != count; ++i) {
indices[i] = i;
}
std::sort(indices, indices + count, [&](int a, int b) {
return row.items[a]->maxWidth() < row.items[b]->maxWidth();
});
auto desiredWidth = row.maxWidth;
row.height = 0;
auto availableWidth = fullWidth
- (st::inlineResultsLeft - st::roundRadiusSmall);
for (auto i = 0; i < count; ++i) {
const auto index = indices[i];
const auto &item = row.items[index];
const auto w = desiredWidth
? (item->maxWidth() * availableWidth / desiredWidth)
: item->maxWidth();
const auto actualWidth = std::max(w, st::inlineResultsMinWidth);
row.height = std::max(
row.height,
item->resizeGetHeight(actualWidth));
if (desiredWidth) {
availableWidth -= actualWidth;
desiredWidth -= row.items[index]->maxWidth();
if (index > 0 && _rightSkip) {
availableWidth -= _rightSkip;
desiredWidth -= _rightSkip;
}
}
}
}
} // namespace Mosaic::Layout

View File

@ -20,388 +20,139 @@ struct FoundItem {
QPoint relative;
};
template <
typename ItemBase,
typename = std::enable_if_t<
std::is_base_of_v<AbstractLayoutItem, ItemBase>>>
class MosaicLayout final {
class AbstractMosaicLayout {
public:
MosaicLayout(int bigWidth)
: _bigWidth(bigWidth) {
}
AbstractMosaicLayout(int bigWidth);
[[nodiscard]] int rowHeightAt(int row) const {
Expects(row >= 0 && row < _rows.size());
return _rows[row].height;
}
[[nodiscard]] int countDesiredHeight(int newWidth) {
auto result = 0;
for (auto &row : _rows) {
layoutRow(row, newWidth ? newWidth : _width);
result += row.height;
}
return result;
}
[[nodiscard]] int rowHeightAt(int row) const;
[[nodiscard]] int countDesiredHeight(int newWidth);
[[nodiscard]] FoundItem findByPoint(const QPoint &globalPoint) const {
auto sx = globalPoint.x() - _offset.x();
auto sy = globalPoint.y() - _offset.y();
auto row = -1;
auto col = -1;
auto sel = -1;
bool exact = true;
if (sy >= 0) {
row = 0;
for (auto rows = rowsCount(); row < rows; ++row) {
const auto rowHeight = _rows[row].height;
if (sy < rowHeight) {
break;
}
sy -= rowHeight;
}
} else {
row = 0;
exact = false;
}
if (row >= rowsCount()) {
row = rowsCount() - 1;
exact = false;
}
if (sx < 0) {
sx = 0;
exact = false;
}
if (sx >= 0 && row >= 0 && row < rowsCount()) {
const auto columnsCount = _rows[row].items.size();
col = 0;
for (int cols = columnsCount; col < cols; ++col) {
const auto item = itemAt(row, col);
const auto width = item->width();
if (sx < width) {
break;
}
sx -= width;
sx -= _rightSkip;
}
if (col >= columnsCount) {
col = columnsCount - 1;
exact = false;
}
[[nodiscard]] FoundItem findByPoint(const QPoint &globalPoint) const;
sel = ::Layout::PositionToIndex(row, + col);
} else {
row = col = -1;
}
return { sel, exact, QPoint(sx, sy) };
}
[[nodiscard]] QRect findRect(int index) const;
[[nodiscard]] QRect findRect(int index) const {
const auto clip = QRect(0, 0, _width, 100);
const auto fromX = rtl()
? (_width - clip.x() - clip.width())
: clip.x();
const auto toX = rtl()
? (_width - clip.x())
: (clip.x() + clip.width());
const auto rows = _rows.size();
auto top = 0;
for (auto row = 0; row != rows; ++row) {
auto &inlineRow = _rows[row];
// if ((top + inlineRow.height) > clip.top()) {
auto left = 0;
if (row == (rows - 1)) {
// context.lastRow = true;
}
for (const auto &item : inlineRow.items) {
if (left >= toX) {
break;
}
void setRightSkip(int rightSkip);
void setOffset(int left, int top);
void setFullWidth(int w);
const auto w = item->width();
if ((left + w) > fromX) {
if (item->position() == index) {
return QRect(
left + _offset.x(),
top + _offset.y(),
item->width(),
item->height());
}
}
left += w;
left += _rightSkip;
}
// }
top += inlineRow.height;
}
return QRect();
}
[[nodiscard]] bool empty() const;
[[nodiscard]] int rowsCount() const;
void addItems(const std::vector<not_null<ItemBase*>> &items) {
_rows.reserve(items.size());
auto row = Row();
row.items.reserve(kInlineItemsMaxPerRow);
auto sumWidth = 0;
for (const auto &item : items) {
addItem(item, row, sumWidth);
}
rowFinalize(row, sumWidth, true);
}
void clearRows(bool resultsDeleted);
void setRightSkip(int rightSkip) {
_rightSkip = rightSkip;
}
void setOffset(int left, int top) {
_offset = { left, top };
}
void setFullWidth(int w) {
_width = w;
}
[[nodiscard]] bool empty() const {
return _rows.empty();
}
protected:
void addItems(gsl::span<const not_null<AbstractLayoutItem*>> items);
[[nodiscard]] int rowsCount() const {
return _rows.size();
}
[[nodiscard]] not_null<AbstractLayoutItem*> itemAt(int row, int column) const;
[[nodiscard]] not_null<AbstractLayoutItem*> itemAt(int index) const;
[[nodiscard]] not_null<ItemBase*> itemAt(int row, int column) const {
Expects((row >= 0)
&& (row < _rows.size())
&& (column >= 0)
&& (column < _rows[row].items.size()));
return _rows[row].items[column];
}
[[nodiscard]] AbstractLayoutItem *maybeItemAt(int row, int column) const;
[[nodiscard]] AbstractLayoutItem *maybeItemAt(int index) const;
[[nodiscard]] not_null<ItemBase*> itemAt(int index) const {
const auto &[row, column] = ::Layout::IndexToPosition(index);
return itemAt(row, column);
}
[[nodiscard]] ItemBase *maybeItemAt(int row, int column) const {
if ((row >= 0)
&& (row < _rows.size())
&& (column >= 0)
&& (column < _rows[row].items.size())) {
return _rows[row].items[column];
}
return nullptr;
}
[[nodiscard]] ItemBase *maybeItemAt(int index) const {
const auto &[row, column] = ::Layout::IndexToPosition(index);
return maybeItemAt(row, column);
}
void clearRows(bool resultsDeleted) {
if (!resultsDeleted) {
for (const auto &row : _rows) {
for (const auto &item : row.items) {
item->setPosition(-1);
}
}
}
_rows.clear();
}
void forEach(Fn<void(const not_null<ItemBase*>)> callback) {
for (const auto &row : _rows) {
for (const auto &item : row.items) {
callback(item);
}
}
}
void forEach(Fn<void(not_null<const AbstractLayoutItem*>)> callback);
void paint(
Fn<void(const not_null<ItemBase*>, QPoint)> paintItemCallback,
const QRect &clip) const {
auto top = _offset.y();
const auto fromX = rtl()
? (_width - clip.x() - clip.width())
: clip.x();
const auto toX = rtl()
? (_width - clip.x())
: (clip.x() + clip.width());
const auto rows = _rows.size();
for (auto row = 0; row != rows; ++row) {
if (top >= clip.top() + clip.height()) {
break;
}
auto &inlineRow = _rows[row];
if ((top + inlineRow.height) > clip.top()) {
auto left = _offset.x();
if (row == (rows - 1)) {
// context.lastRow = true;
}
for (const auto &item : inlineRow.items) {
if (left >= toX) {
break;
}
const auto w = item->width();
if ((left + w) > fromX) {
paintItemCallback(item, QPoint(left, top));
}
left += w;
left += _rightSkip;
}
}
top += inlineRow.height;
}
}
Fn<void(not_null<AbstractLayoutItem*>, QPoint)> paintItem,
const QRect &clip) const;
int validateExistingRows(
Fn<bool(const not_null<ItemBase*>, int)> checkItemCallback,
int count) {
auto until = 0;
auto untilRow = 0;
auto untilCol = 0;
while (until < count) {
auto &rowItems = _rows[untilRow].items;
if ((untilRow >= _rows.size())
|| checkItemCallback(rowItems[untilCol], until)) {
break;
}
++until;
if (++untilCol == rowItems.size()) {
++untilRow;
untilCol = 0;
}
}
if (until == count) { // All items are layed out.
if (untilRow == _rows.size()) { // Nothing changed.
return until;
}
{
const auto rows = _rows.size();
auto skip = untilCol;
for (auto i = untilRow; i < rows; ++i) {
for (const auto &item : _rows[i].items) {
if (skip) {
--skip;
} else {
item->setPosition(-1);
}
}
}
}
if (!untilCol) { // All good rows are filled.
_rows.resize(untilRow);
return until;
}
_rows.resize(untilRow + 1);
_rows[untilRow].items.resize(untilCol);
_rows[untilRow].maxWidth = ranges::accumulate(
_rows[untilRow].items,
0,
[](int w, auto &row) { return w + row->maxWidth(); });
layoutRow(_rows[untilRow], _width);
return until;
}
if (untilRow && !untilCol) { // Remove last row, maybe it is not full.
--untilRow;
untilCol = _rows[untilRow].items.size();
}
until -= untilCol;
for (auto i = untilRow; i < _rows.size(); ++i) {
for (const auto &item : _rows[i].items) {
item->setPosition(-1);
}
}
_rows.resize(untilRow);
return until;
}
Fn<bool(not_null<const AbstractLayoutItem*>, int)> checkItem,
int count);
private:
static constexpr auto kInlineItemsMaxPerRow = 5;
struct Row {
int maxWidth = 0;
int height = 0;
std::vector<ItemBase*> items;
std::vector<AbstractLayoutItem*> items;
};
void addItem(not_null<ItemBase*> item, Row &row, int &sumWidth) {
// item->preload();
using namespace ::Layout;
item->setPosition(PositionToIndex(_rows.size(), row.items.size()));
if (rowFinalize(row, sumWidth, false)) {
item->setPosition(PositionToIndex(_rows.size(), 0));
}
sumWidth += item->maxWidth();
if (!row.items.empty() && _rightSkip) {
sumWidth += _rightSkip;
}
row.items.push_back(item);
}
bool rowFinalize(Row &row, int &sumWidth, bool force) {
if (row.items.empty()) {
return false;
}
const auto full = (row.items.size() >= kInlineItemsMaxPerRow);
// Currently use the same GIFs layout for all widget sizes.
const auto big = (sumWidth >= _bigWidth);
if (full || big || force) {
row.maxWidth = (full || big) ? sumWidth : 0;
layoutRow(row, _width);
_rows.push_back(std::move(row));
row = Row();
row.items.reserve(kInlineItemsMaxPerRow);
sumWidth = 0;
return true;
}
return false;
}
void layoutRow(Row &row, int fullWidth) {
const auto count = int(row.items.size());
Assert(count <= kInlineItemsMaxPerRow);
// Enumerate items in the order of growing maxWidth()
// for that sort item indices by maxWidth().
int indices[kInlineItemsMaxPerRow];
for (auto i = 0; i != count; ++i) {
indices[i] = i;
}
std::sort(indices, indices + count, [&](int a, int b) {
return row.items[a]->maxWidth() < row.items[b]->maxWidth();
});
auto desiredWidth = row.maxWidth;
row.height = 0;
auto availableWidth = fullWidth
- (st::inlineResultsLeft - st::roundRadiusSmall);
for (auto i = 0; i < count; ++i) {
const auto index = indices[i];
const auto &item = row.items[index];
const auto w = desiredWidth
? (item->maxWidth() * availableWidth / desiredWidth)
: item->maxWidth();
const auto actualWidth = std::max(w, st::inlineResultsMinWidth);
row.height = std::max(
row.height,
item->resizeGetHeight(actualWidth));
if (desiredWidth) {
availableWidth -= actualWidth;
desiredWidth -= row.items[index]->maxWidth();
if (index > 0 && _rightSkip) {
availableWidth -= _rightSkip;
desiredWidth -= _rightSkip;
}
}
}
}
void addItem(not_null<AbstractLayoutItem*> item, Row &row, int &sumWidth);
bool rowFinalize(Row &row, int &sumWidth, bool force);
void layoutRow(Row &row, int fullWidth);
int _bigWidth;
int _width = 0;
int _rightSkip = 0;
QPoint _offset;
std::vector<Row> _rows;
};
template <
typename ItemBase,
typename = std::enable_if_t<
std::is_base_of_v<AbstractLayoutItem, ItemBase>>>
class MosaicLayout final : public AbstractMosaicLayout {
using Parent = AbstractMosaicLayout;
public:
using Parent::Parent;
void addItems(const std::vector<not_null<ItemBase*>> &items) {
Parent::addItems({
reinterpret_cast<const not_null<AbstractLayoutItem*>*>(
items.data()),
items.size() });
}
[[nodiscard]] not_null<ItemBase*> itemAt(int row, int column) const {
return Downcast(Parent::itemAt(row, column));
}
[[nodiscard]] not_null<ItemBase*> itemAt(int index) const {
return Downcast(Parent::itemAt(index));
}
[[nodiscard]] ItemBase *maybeItemAt(int row, int column) const {
return Downcast(Parent::maybeItemAt(row, column));
}
[[nodiscard]] ItemBase *maybeItemAt(int index) const {
return Downcast(Parent::maybeItemAt(index));
}
void forEach(Fn<void(not_null<const ItemBase*>)> callback) {
Parent::forEach([&](
not_null<const AbstractLayoutItem*> item) {
callback(Downcast(item));
});
}
void paint(
Fn<void(not_null<ItemBase*>, QPoint)> paintItem,
const QRect &clip) const {
Parent::paint([&](
not_null<AbstractLayoutItem*> item,
QPoint point) {
paintItem(Downcast(item), point);
}, clip);
}
int validateExistingRows(
Fn<bool(not_null<const ItemBase*>, int)> checkItem,
int count) {
return Parent::validateExistingRows([&](
not_null<const AbstractLayoutItem*> item,
int until) {
return checkItem(Downcast(item), until);
}, count);
}
private:
[[nodiscard]] static not_null<ItemBase*> Downcast(
not_null<AbstractLayoutItem*> item) {
return static_cast<ItemBase*>(item.get());
}
[[nodiscard]] static ItemBase *Downcast(
AbstractLayoutItem *item) {
return static_cast<ItemBase*>(item);
}
[[nodiscard]] static not_null<const ItemBase*> Downcast(
not_null<const AbstractLayoutItem*> item) {
return static_cast<const ItemBase*>(item.get());
}
[[nodiscard]] static const ItemBase *Downcast(
const AbstractLayoutItem *item) {
return static_cast<const ItemBase*>(item);
}
};
} // namespace Mosaic::Layout

View File

@ -63,6 +63,7 @@ PRIVATE
layout/abstract_layout_item.cpp
layout/abstract_layout_item.h
layout/layout_mosaic.cpp
layout/layout_mosaic.h
layout/layout_position.cpp
layout/layout_position.h