diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 35fe93f559..4142d74ef7 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -195,6 +195,11 @@ GifsListWidget::GifsListWidget( update(); } }, lifetime()); + + sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + _mosaic.setFullWidth(s.width()); + }, lifetime()); } rpl::producer GifsListWidget::fileChosen() const { @@ -238,12 +243,7 @@ void GifsListWidget::checkLoadMore() { } int GifsListWidget::countDesiredHeight(int newWidth) { - auto result = st::stickerPanPadding; - for (int i = 0, l = _rows.count(); i < l; ++i) { - layoutInlineRow(_rows[i], newWidth); - result += _rows[i].height; - } - return result + st::stickerPanPadding; + return _mosaic.countDesiredHeight(newWidth) + st::stickerPanPadding * 2; } GifsListWidget::~GifsListWidget() { @@ -321,7 +321,7 @@ void GifsListWidget::paintEvent(QPaintEvent *e) { } void GifsListWidget::paintInlineItems(Painter &p, QRect clip) { - if (_rows.isEmpty()) { + if (_mosaic.empty()) { p.setFont(st::normalFont); p.setPen(st::noContactsColor); auto text = _inlineQuery.isEmpty() @@ -333,35 +333,12 @@ void GifsListWidget::paintInlineItems(Painter &p, QRect clip) { auto gifPaused = controller()->isGifPausedAtLeastFor(Window::GifPauseReason::SavedGifs); InlineBots::Layout::PaintContext context(crl::now(), false, gifPaused, false); - auto top = st::stickerPanPadding; - auto fromx = rtl() ? (width() - clip.x() - clip.width()) : clip.x(); - auto tox = rtl() ? (width() - clip.x()) : (clip.x() + clip.width()); - for (auto row = 0, rows = _rows.size(); row != rows; ++row) { - auto &inlineRow = _rows[row]; - if (top >= clip.top() + clip.height()) { - break; - } - if (top + inlineRow.height > clip.top()) { - auto left = st::inlineResultsLeft - st::roundRadiusSmall; - if (row == rows - 1) context.lastRow = true; - for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { - if (left >= tox) break; - - auto item = inlineRow.items.at(col); - auto w = item->width(); - if (left + w > fromx) { - p.translate(left, top); - item->paint(p, clip.translated(-left, -top), &context); - p.translate(-left, -top); - } - left += w; - if (item->hasRightSkip()) { - left += st::inlineResultsSkip; - } - } - } - top += inlineRow.height; - } + _mosaic.paint( + p, + st::stickerPanPadding, + st::inlineResultsLeft - st::roundRadiusSmall, + clip, + context); } void GifsListWidget::mousePressEvent(QMouseEvent *e) { @@ -394,8 +371,7 @@ void GifsListWidget::fillContextMenu( SendMenu::DefaultSilentCallback(send), SendMenu::DefaultScheduleCallback(this, type, send)); - if (!(row >= _rows.size() || column >= _rows[row].items.size())) { - const auto item = _rows[row].items[column]; + if (const auto item = _mosaic.maybeItemAt(row, column)) { const auto document = item->getDocument() ? item->getDocument() // Saved GIF. : item->getPreviewDocument(); // Searched GIF. @@ -443,13 +419,13 @@ void GifsListWidget::selectInlineResult( int column, Api::SendOptions options, bool forceSend) { - if (row >= _rows.size() || column >= _rows[row].items.size()) { + const auto item = _mosaic.maybeItemAt(row, column); + if (!item) { return; } forceSend |= (QGuiApplication::keyboardModifiers() == Qt::ControlModifier); - auto item = _rows[row].items[column]; if (const auto photo = item->getPhoto()) { using Data::PhotoSize; const auto media = photo->activeMediaView(); @@ -506,8 +482,7 @@ void GifsListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { void GifsListWidget::clearSelection() { if (_selected >= 0) { int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; - Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows[srow].items.size()); - ClickHandler::clearActive(_rows[srow].items[scol]); + ClickHandler::clearActive(_mosaic.itemAt(srow, scol)); setCursor(style::cur_default); } _selected = _pressed = -1; @@ -538,65 +513,21 @@ void GifsListWidget::clearHeavyData() { } } -bool GifsListWidget::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth) { - LayoutItem *layout = nullptr; - if (savedGif) { - layout = layoutPrepareSavedGif(savedGif, (_rows.size() * MatrixRowShift) + row.items.size()); - } else if (result) { - layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); - } - if (!layout) return false; - - layout->preload(); - if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { - layout->setPosition(_rows.size() * MatrixRowShift); - } - - sumWidth += layout->maxWidth(); - if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { - sumWidth += st::inlineResultsSkip; - } - - row.items.push_back(layout); - return true; -} - -bool GifsListWidget::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { - if (row.items.isEmpty()) return false; - - auto full = (row.items.size() >= kInlineItemsMaxPerRow); - - // Currently use the same GIFs layout for all widget sizes. -// auto big = (sumWidth >= st::roundRadiusSmall + width() - st::inlineResultsLeft); - auto big = (sumWidth >= st::emojiPanWidth - st::inlineResultsLeft); - if (full || big || force) { - row.maxWidth = (full || big) ? sumWidth : 0; - layoutInlineRow( - row, - width()); - _rows.push_back(row); - row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - sumWidth = 0; - return true; - } - return false; -} - void GifsListWidget::refreshSavedGifs() { if (_section == Section::Gifs) { clearInlineRows(false); const auto &saved = controller()->session().data().stickers().savedGifs(); if (!saved.isEmpty()) { - _rows.reserve(saved.size()); - auto row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - auto sumWidth = 0; - for (const auto &gif : saved) { - inlineRowsAddItem(gif, 0, row, sumWidth); - } - inlineRowFinalize(row, sumWidth, true); + const auto layouts = ranges::views::all( + saved + ) | ranges::views::transform([&](not_null gif) { + return layoutPrepareSavedGif(gif); + }) | ranges::views::filter([](const LayoutItem *item) { + return item != nullptr; + }) | ranges::to_vector; + + _mosaic.addItems(layouts); } deleteUnusedGifLayouts(); @@ -616,18 +547,12 @@ void GifsListWidget::clearInlineRows(bool resultsDeleted) { _selected = _pressed = -1; } else { clearSelection(); - for (const auto &row : std::as_const(_rows)) { - for (const auto &item : std::as_const(row.items)) { - item->setPosition(-1); - } - } } - _rows.clear(); + _mosaic.clearRows(resultsDeleted); } GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif( - not_null document, - int32 position) { + not_null document) { auto it = _gifLayouts.find(document); if (it == _gifLayouts.cend()) { if (auto layout = LayoutItem::createLayoutGif(this, document)) { @@ -639,13 +564,11 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif( } if (!it->second->maxWidth()) return nullptr; - it->second->setPosition(position); return it->second.get(); } GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult( - not_null result, - int32 position) { + not_null result) { auto it = _inlineLayouts.find(result); if (it == _inlineLayouts.cend()) { if (auto layout = LayoutItem::createLayout( @@ -660,12 +583,11 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult( } if (!it->second->maxWidth()) return nullptr; - it->second->setPosition(position); return it->second.get(); } void GifsListWidget::deleteUnusedGifLayouts() { - if (_rows.isEmpty() || _section != Section::Gifs) { // delete all + if (_mosaic.empty() || _section != Section::Gifs) { // delete all _gifLayouts.clear(); } else { for (auto i = _gifLayouts.begin(); i != _gifLayouts.cend();) { @@ -679,7 +601,7 @@ void GifsListWidget::deleteUnusedGifLayouts() { } void GifsListWidget::deleteUnusedInlineLayouts() { - if (_rows.isEmpty() || _section == Section::Gifs) { // delete all + if (_mosaic.empty() || _section == Section::Gifs) { // delete all _inlineLayouts.clear(); } else { for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { @@ -692,49 +614,8 @@ void GifsListWidget::deleteUnusedInlineLayouts() { } } -void GifsListWidget::layoutInlineRow(Row &row, int fullWidth) { - 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; - int availw = fullWidth - (st::inlineResultsLeft - st::roundRadiusSmall); - for (int i = 0; i < count; ++i) { - const auto index = indices[i]; - const auto &item = row.items[index]; - const auto w = desiredWidth - ? (item->maxWidth() * availw / desiredWidth) - : item->maxWidth(); - auto actualw = qMax(w, st::inlineResultsMinWidth); - row.height = qMax(row.height, item->resizeGetHeight(actualw)); - if (desiredWidth) { - availw -= actualw; - desiredWidth -= row.items[index]->maxWidth(); - if (index > 0 && row.items[index - 1]->hasRightSkip()) { - availw -= st::inlineResultsSkip; - desiredWidth -= st::inlineResultsSkip; - } - } - } -} - void GifsListWidget::preloadImages() { - for (auto row = 0, rows = _rows.size(); row != rows; ++row) { - for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { - _rows[row].items[col]->preload(); - } - } + _mosaic.preloadImages(); } void GifsListWidget::switchToSavedGifs() { @@ -757,20 +638,22 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result clearSelection(); _section = Section::Inlines; - auto count = int(entry->results.size()); - auto from = validateExistingInlineRows(entry->results); + const auto count = int(entry->results.size()); + const auto from = validateExistingInlineRows(entry->results); auto added = 0; if (count) { - _rows.reserve(count); - auto row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - auto sumWidth = 0; - for (auto i = from; i != count; ++i) { - if (inlineRowsAddItem(0, entry->results[i].get(), row, sumWidth)) { - ++added; - } - } - inlineRowFinalize(row, sumWidth, true); + const auto resultLayouts = entry->results | ranges::views::slice( + from, + count + ) | ranges::views::transform([&]( + const std::unique_ptr &r) { + return layoutPrepareInlineResult(r.get()); + }) | ranges::views::filter([](const LayoutItem *item) { + return item != nullptr; + }) | ranges::to_vector; + + _mosaic.addItems(resultLayouts); + added = resultLayouts.size(); } resizeToWidth(width()); @@ -783,61 +666,11 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result } int GifsListWidget::validateExistingInlineRows(const InlineResults &results) { - int count = results.size(), until = 0, untilrow = 0, untilcol = 0; - for (; until < count;) { - if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { - break; - } - ++until; - if (++untilcol == _rows[untilrow].items.size()) { - ++untilrow; - untilcol = 0; - } - } - if (until == count) { // all items are layed out - if (untilrow == _rows.size()) { // nothing changed - return until; - } + const auto until = _mosaic.validateExistingRows(results); - for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { - for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { - if (skip) { - --skip; - } else { - _rows[i].items[j]->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 = std::accumulate( - _rows[untilrow].items.begin(), - _rows[untilrow].items.end(), - 0, - [](int w, auto &row) { return w + row->maxWidth(); }); - layoutInlineRow(_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 (int i = untilrow, l = _rows.size(); i < l; ++i) { - for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { - _rows[i].items[j]->setPosition(-1); - } - } - _rows.resize(untilrow); - - if (_rows.isEmpty()) { + if (_mosaic.empty()) { _inlineWithThumb = false; - for (int i = until; i < count; ++i) { + for (int i = until; i < results.size(); ++i) { if (results.at(i)->hasThumbDisplay()) { _inlineWithThumb = true; break; @@ -853,8 +686,8 @@ void GifsListWidget::inlineItemLayoutChanged(const InlineBots::Layout::ItemBase } int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row < _rows.size() && col < _rows[row].items.size()) { - if (layout == _rows[row].items[col]) { + if (const auto item = _mosaic.maybeItemAt(row, col)) { + if (layout == item) { updateSelected(); } } @@ -877,14 +710,14 @@ bool GifsListWidget::inlineItemVisible(const InlineBots::Layout::ItemBase *layou auto row = position / MatrixRowShift; auto col = position % MatrixRowShift; - Assert((row < _rows.size()) && (col < _rows[row].items.size())); auto top = 0; for (auto i = 0; i != row; ++i) { - top += _rows[i].height; + top += _mosaic.rowHeightAt(i); } - return (top < getVisibleBottom()) && (top + _rows[row].items[col]->height() > getVisibleTop()); + return (top < getVisibleBottom()) + && (top + _mosaic.itemAt(row, col)->height() > getVisibleTop()); } Data::FileOrigin GifsListWidget::inlineItemFileOrigin() { @@ -1035,33 +868,36 @@ void GifsListWidget::updateSelected() { ClickHandlerHost *lnkhost = nullptr; if (sy >= 0) { row = 0; - for (int rows = _rows.size(); row < rows; ++row) { - if (sy < _rows[row].height) { + for (int rows = _mosaic.rowsCount(); row < rows; ++row) { + const auto rowHeight = _mosaic.rowHeightAt(row); + if (sy < rowHeight) { break; } - sy -= _rows[row].height; + sy -= rowHeight; } } - if (sx >= 0 && row >= 0 && row < _rows.size()) { - auto &inlineItems = _rows[row].items; + if (sx >= 0 && row >= 0 && row < _mosaic.rowsCount()) { + const auto columnsCount = _mosaic.columnsCountAt(row); col = 0; - for (int cols = inlineItems.size(); col < cols; ++col) { - int width = inlineItems[col]->width(); + for (int cols = columnsCount; col < cols; ++col) { + const auto item = _mosaic.itemAt(row, col); + int width = item->width(); if (sx < width) { break; } sx -= width; - if (inlineItems[col]->hasRightSkip()) { + if (item->hasRightSkip()) { sx -= st::inlineResultsSkip; } } - if (col < inlineItems.size()) { + if (col < columnsCount) { + const auto item = _mosaic.itemAt(row, col); sel = row * MatrixRowShift + col; - auto result = inlineItems[col]->getState( + auto result = item->getState( QPoint(sx, sy), HistoryView::StateRequest()); lnk = result.link; - lnkhost = inlineItems[col]; + lnkhost = item; } else { row = col = -1; } @@ -1072,18 +908,16 @@ void GifsListWidget::updateSelected() { int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; if (_selected != sel) { if (srow >= 0 && scol >= 0) { - Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows[srow].items.size()); - _rows[srow].items[scol]->update(); + _mosaic.itemAt(srow, scol)->update(); } _selected = sel; if (row >= 0 && col >= 0) { - Assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows[row].items.size()); - _rows[row].items[col]->update(); + _mosaic.itemAt(row, col)->update(); } if (_previewShown && _selected >= 0 && _pressed != _selected) { _pressed = _selected; if (row >= 0 && col >= 0) { - auto layout = _rows[row].items[col]; + const auto layout = _mosaic.itemAt(row, col); if (const auto previewDocument = layout->getPreviewDocument()) { controller()->widget()->showMediaPreview( Data::FileOriginSavedGifs(), @@ -1106,8 +940,7 @@ void GifsListWidget::showPreview() { return; } int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; - if (row < _rows.size() && col < _rows[row].items.size()) { - auto layout = _rows[row].items[col]; + if (const auto layout = _mosaic.maybeItemAt(row, col)) { if (const auto previewDocument = layout->getPreviewDocument()) { _previewShown = controller()->widget()->showMediaPreview( Data::FileOriginSavedGifs(), diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h index 279934ea6a..5122f9cc56 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_selector.h" #include "base/timer.h" #include "inline_bots/inline_bot_layout_item.h" +#include "inline_bots/inline_results_mosaic_layout.h" #include "app.h" #include @@ -142,32 +143,18 @@ private: base::Timer _updateInlineItems; bool _inlineWithThumb = false; - struct Row { - int maxWidth = 0; - int height = 0; - QVector items; - }; - QVector _rows; void clearInlineRows(bool resultsDeleted); std::map< not_null, std::unique_ptr> _gifLayouts; - LayoutItem *layoutPrepareSavedGif( - not_null document, - int32 position); + LayoutItem *layoutPrepareSavedGif(not_null document); std::map< not_null, std::unique_ptr> _inlineLayouts; - LayoutItem *layoutPrepareInlineResult( - not_null result, - int32 position); + LayoutItem *layoutPrepareInlineResult(not_null result); - bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth); - bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); - - void layoutInlineRow(Row &row, int fullWidth); void deleteUnusedGifLayouts(); void deleteUnusedInlineLayouts(); @@ -182,6 +169,8 @@ private: Footer *_footer = nullptr; + InlineBots::Layout::MosaicLayout _mosaic; + int _selected = -1; int _pressed = -1; QPoint _lastMousePos;