From 868c49429921e0de50c2e7c8855ff7db9c35a18b Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 28 Jul 2021 17:00:46 +0300 Subject: [PATCH] Extract Mosaic::Layout::AbstractMosaicLayout. --- .../chat_helpers/gifs_list_widget.cpp | 6 +- .../info/media/info_media_list_widget.cpp | 2 +- .../inline_bots/inline_results_inner.cpp | 6 +- Telegram/SourceFiles/layout/layout_mosaic.cpp | 391 +++++++++++++++ Telegram/SourceFiles/layout/layout_mosaic.h | 459 ++++-------------- Telegram/cmake/td_ui.cmake | 1 + 6 files changed, 504 insertions(+), 361 deletions(-) create mode 100644 Telegram/SourceFiles/layout/layout_mosaic.cpp diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index ada995951..dd4bc60fe 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -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 item, QPoint point) { + auto paintItem = [&](not_null 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 item) { + _mosaic.forEach([](not_null 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 item, + not_null item, int untilIndex) { return item->getResult() != results[untilIndex].get(); }, results.size()); diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index e8b437a00..85cd6dafa 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -445,7 +445,7 @@ void ListWidget::Section::paint( localContext.isAfterDate = (header > 0); if (!_mosaic.empty()) { - auto paintItem = [&](const not_null item, QPoint point) { + auto paintItem = [&](not_null item, QPoint point) { p.translate(point.x(), point.y()); item->paint( p, diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 2950a32a8..9d17f00b7 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -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 item, QPoint point) { + auto paintItem = [&](not_null 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 item) { + _mosaic.forEach([](not_null 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 item, + not_null item, int untilIndex) { return item->getResult() != results[untilIndex].get(); }, results.size()); diff --git a/Telegram/SourceFiles/layout/layout_mosaic.cpp b/Telegram/SourceFiles/layout/layout_mosaic.cpp new file mode 100644 index 000000000..48c29b1e6 --- /dev/null +++ b/Telegram/SourceFiles/layout/layout_mosaic.cpp @@ -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> 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 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 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)> callback) { + for (const auto &row : _rows) { + for (const auto &item : row.items) { + callback(item); + } + } +} + +void AbstractMosaicLayout::paint( + Fn, 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, 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 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 diff --git a/Telegram/SourceFiles/layout/layout_mosaic.h b/Telegram/SourceFiles/layout/layout_mosaic.h index c159a3b8d..773f4abe4 100644 --- a/Telegram/SourceFiles/layout/layout_mosaic.h +++ b/Telegram/SourceFiles/layout/layout_mosaic.h @@ -20,388 +20,139 @@ struct FoundItem { QPoint relative; }; -template < - typename ItemBase, - typename = std::enable_if_t< - std::is_base_of_v>> -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> &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> items); - [[nodiscard]] int rowsCount() const { - return _rows.size(); - } + [[nodiscard]] not_null itemAt(int row, int column) const; + [[nodiscard]] not_null itemAt(int index) const; - [[nodiscard]] not_null 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 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)> callback) { - for (const auto &row : _rows) { - for (const auto &item : row.items) { - callback(item); - } - } - } + void forEach(Fn)> callback); void paint( - Fn, 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, QPoint)> paintItem, + const QRect &clip) const; int validateExistingRows( - Fn, 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, int)> checkItem, + int count); private: static constexpr auto kInlineItemsMaxPerRow = 5; struct Row { int maxWidth = 0; int height = 0; - std::vector items; + std::vector items; }; - void addItem(not_null 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 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 _rows; + +}; + +template < + typename ItemBase, + typename = std::enable_if_t< + std::is_base_of_v>> +class MosaicLayout final : public AbstractMosaicLayout { + using Parent = AbstractMosaicLayout; + +public: + using Parent::Parent; + + void addItems(const std::vector> &items) { + Parent::addItems({ + reinterpret_cast*>( + items.data()), + items.size() }); + } + + [[nodiscard]] not_null itemAt(int row, int column) const { + return Downcast(Parent::itemAt(row, column)); + } + [[nodiscard]] not_null 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)> callback) { + Parent::forEach([&]( + not_null item) { + callback(Downcast(item)); + }); + } + + void paint( + Fn, QPoint)> paintItem, + const QRect &clip) const { + Parent::paint([&]( + not_null item, + QPoint point) { + paintItem(Downcast(item), point); + }, clip); + } + + int validateExistingRows( + Fn, int)> checkItem, + int count) { + return Parent::validateExistingRows([&]( + not_null item, + int until) { + return checkItem(Downcast(item), until); + }, count); + } + +private: + [[nodiscard]] static not_null Downcast( + not_null item) { + return static_cast(item.get()); + } + [[nodiscard]] static ItemBase *Downcast( + AbstractLayoutItem *item) { + return static_cast(item); + } + [[nodiscard]] static not_null Downcast( + not_null item) { + return static_cast(item.get()); + } + [[nodiscard]] static const ItemBase *Downcast( + const AbstractLayoutItem *item) { + return static_cast(item); + } + }; } // namespace Mosaic::Layout diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 0f0f2e10f..c886f61a9 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -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