diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 03c7beedd..4c96025a5 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -422,10 +422,7 @@ void Reactions::scheduleMyTagsUpdate() { return; } _myTagsUpdateScheduled = false; - _myTagsIds = _myTagsInfo | ranges::views::transform( - &MyTagInfo::id - ) | ranges::to_vector; - _myTags = resolveByIds(_myTagsIds, _unresolvedMyTags); + _myTags = resolveByInfos(_myTagsInfo, _unresolvedMyTags); _myTagsUpdated.fire({}); }); } @@ -854,10 +851,7 @@ void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) { void Reactions::updateMyTags(const MTPDmessages_savedReactionTags &data) { _myTagsHash = data.vhash().v; _myTagsInfo = ListFromMTP(data); - _myTagsIds = _myTagsInfo | ranges::views::transform( - &MyTagInfo::id - ) | ranges::to_vector; - _myTags = resolveByIds(_myTagsIds, _unresolvedMyTags); + _myTags = resolveByInfos(_myTagsInfo, _unresolvedMyTags); _myTagsUpdated.fire({}); } @@ -927,7 +921,7 @@ void Reactions::customEmojiResolveDone(not_null document) { } if (myTag) { _unresolvedMyTags.erase(k); - _myTags = resolveByIds(_myTagsIds, _unresolvedMyTags); + _myTags = resolveByInfos(_myTagsInfo, _unresolvedMyTags); } if (tag) { _unresolvedTags.erase(l); @@ -980,6 +974,41 @@ std::vector Reactions::resolveByIds( return result; } +std::optional Reactions::resolveByInfo(const MyTagInfo &info) { + const auto withInfo = [&](Reaction reaction) { + reaction.title = info.title; + reaction.count = info.count; + return reaction; + }; + if (const auto emoji = info.id.emoji(); !emoji.isEmpty()) { + const auto i = ranges::find(_available, info.id, &Reaction::id); + if (i != end(_available)) { + return withInfo(*i); + } + } else if (const auto customId = info.id.custom()) { + const auto document = _owner->document(customId); + if (document->sticker()) { + return withInfo(CustomReaction(document)); + } + } + return {}; +} + +std::vector Reactions::resolveByInfos( + const std::vector &infos, + base::flat_set &unresolved) { + auto result = std::vector(); + result.reserve(infos.size()); + for (const auto &tag : infos) { + if (const auto resolved = resolveByInfo(tag)) { + result.push_back(*resolved); + } else if (unresolved.emplace(tag.id).second) { + resolve(tag.id); + } + } + return result; +} + void Reactions::resolve(const ReactionId &id) { if (const auto emoji = id.emoji(); !emoji.isEmpty()) { refreshDefault(); diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 5e8721665..1475df7b4 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -34,6 +34,7 @@ struct Reaction { //not_null activateEffects; DocumentData *centerIcon = nullptr; DocumentData *aroundAnimation = nullptr; + int count = 0; bool active = false; bool premium = false; }; @@ -167,6 +168,11 @@ private: [[nodiscard]] std::vector resolveByIds( const std::vector &ids, base::flat_set &unresolved); + [[nodiscard]] std::optional resolveByInfo( + const MyTagInfo &info); + [[nodiscard]] std::vector resolveByInfos( + const std::vector &infos, + base::flat_set &unresolved); void resolve(const ReactionId &id); void applyFavorite(const ReactionId &id); void scheduleMyTagsUpdate(); @@ -193,7 +199,6 @@ private: std::vector _recentIds; base::flat_set _unresolvedRecent; std::vector _myTags; - std::vector _myTagsIds; std::vector _myTagsInfo; base::flat_set _unresolvedMyTags; std::vector _tags; diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp index 7f0aa16eb..7bf857ba7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp @@ -19,10 +19,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_dialogs.h" namespace Dialogs { +namespace { + +[[nodiscard]] QString ComposeText(const Data::Reaction &tag) { + auto result = tag.title; + if (!result.isEmpty() && tag.count > 0) { + result.append(' '); + } + if (tag.count > 0) { + result.append(QString::number(tag.count)); + } + return TextUtilities::SingleLine(result); +} + +} // namespace struct SearchTags::Tag { Data::ReactionId id; std::unique_ptr custom; + QString text; + int textWidth = 0; mutable QImage image; QRect geometry; ClickHandlerPtr link; @@ -75,7 +91,7 @@ void SearchTags::fill(const std::vector &list) { } })); }; - const auto push = [&](Data::ReactionId id) { + const auto push = [&](Data::ReactionId id, const QString &text) { const auto customId = id.custom(); _tags.push_back({ .id = id, @@ -84,6 +100,8 @@ void SearchTags::fill(const std::vector &list) { customId, [=] { _repaintRequests.fire({}); }) : nullptr), + .text = text, + .textWidth = st::reactionInlineTagFont->width(text), .link = link(id), .selected = ranges::contains(selected, id), }); @@ -92,11 +110,11 @@ void SearchTags::fill(const std::vector &list) { } }; for (const auto &reaction : list) { - push(reaction.id); + push(reaction.id, ComposeText(reaction)); } for (const auto &reaction : _added) { if (!ranges::contains(_tags, reaction, &Tag::id)) { - push(reaction); + push(reaction, QString()); } } if (_width > 0) { @@ -107,26 +125,27 @@ void SearchTags::fill(const std::vector &list) { void SearchTags::layout() { Expects(_width > 0); + if (_tags.empty()) { + _height = 0; + return; + } const auto &bg = validateBg(false); const auto skip = st::dialogsSearchTagSkip; const auto size = bg.size() / bg.devicePixelRatio(); - const auto xsingle = size.width() + skip.x(); - const auto ysingle = size.height() + skip.y(); - const auto columns = std::max((_width + skip.x()) / xsingle, 1); - const auto rows = (_tags.size() + columns - 1) / columns; - for (auto row = 0; row != rows; ++row) { - for (auto column = 0; column != columns; ++column) { - const auto index = row * columns + column; - if (index >= _tags.size()) { - break; - } - const auto x = column * xsingle; - const auto y = row * ysingle; - _tags[index].geometry = QRect(QPoint(x, y), size); + const auto xbase = size.width(); + const auto ybase = size.height(); + auto x = 0; + auto y = 0; + for (auto &tag : _tags) { + const auto width = xbase + tag.textWidth; + if (x > 0 && x + width > _width) { + x = 0; + y += ybase + skip.y(); } + tag.geometry = QRect(x, y, width, xbase); + x += width + skip.x(); } - const auto bottom = st::dialogsSearchTagBottom; - _height = rows ? (rows * ysingle - skip.y() + bottom) : 0; + _height = y + ybase + st::dialogsSearchTagBottom; } void SearchTags::resizeToWidth(int width) { @@ -212,7 +231,8 @@ void SearchTags::paint( const auto padding = st::reactionInlinePadding; for (const auto &tag : _tags) { const auto geometry = tag.geometry.translated(position); - p.drawImage(geometry.topLeft(), validateBg(tag.selected)); + paintBackground(p, geometry, tag.selected); + paintText(p, geometry, tag); if (!tag.custom && tag.image.isNull()) { tag.image = _owner->reactions().resolveImageFor( tag.id, @@ -239,16 +259,59 @@ void SearchTags::paint( } } +void SearchTags::paintBackground( + QPainter &p, + QRect geometry, + bool selected) const { + const auto &image = validateBg(selected); + const auto ratio = int(image.devicePixelRatio()); + const auto size = image.size() / ratio; + if (const auto fill = geometry.width() - size.width(); fill > 0) { + const auto left = size.width() / 2; + const auto right = size.width() - left; + const auto x = geometry.x(); + const auto y = geometry.y(); + p.drawImage( + QRect(x, y, left, size.height()), + image, + QRect(QPoint(), QSize(left, size.height()) * ratio)); + p.fillRect( + QRect(x + left, y, fill, size.height()), + bgColor(selected)); + p.drawImage( + QRect(x + left + fill, y, right, size.height()), + image, + QRect(left * ratio, 0, right * ratio, size.height() * ratio)); + } else { + p.drawImage(geometry.topLeft(), image); + } +} + +void SearchTags::paintText(QPainter &p, QRect geometry, const Tag &tag) const { + using namespace HistoryView::Reactions; + + if (tag.text.isEmpty()) { + return; + } + p.setPen(tag.selected ? st::dialogsTextFgActive : st::windowSubTextFg); + p.setFont(st::reactionInlineTagFont); + const auto x = geometry.x() + st::reactionInlineTagNamePosition.x(); + const auto y = geometry.y() + st::reactionInlineTagNamePosition.y(); + p.drawText(x, y + st::reactionInlineTagFont->ascent, tag.text); +} + +QColor SearchTags::bgColor(bool selected) const { + return selected + ? st::dialogsBgActive->c + : st::dialogsBgOver->c; +} + const QImage &SearchTags::validateBg(bool selected) const { using namespace HistoryView::Reactions; auto &image = selected ? _selectedBg : _normalBg; if (image.isNull()) { - const auto tagBg = selected - ? st::dialogsBgActive->c - : st::dialogsBgOver->c; - const auto dotBg = selected - ? anim::with_alpha(tagBg, InlineList::TagDotAlpha()) - : st::windowSubTextFg->c; + const auto tagBg = bgColor(selected); + const auto dotBg = st::transparent->c; image = InlineList::PrepareTagBg(tagBg, dotBg); } return image; diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.h b/Telegram/SourceFiles/dialogs/dialogs_search_tags.h index 4c52162eb..01b56e5e9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_tags.h +++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.h @@ -59,7 +59,13 @@ private: const QColor &textColor) const; void layout(); [[nodiscard]] std::vector collectSelected() const; + [[nodiscard]] QColor bgColor(bool selected) const; [[nodiscard]] const QImage &validateBg(bool selected) const; + void paintBackground( + QPainter &p, + QRect geometry, + bool selected) const; + void paintText(QPainter &p, QRect geometry, const Tag &tag) const; const not_null _owner; std::vector _added; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index 39ae8087a..ab4c23c2e 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -51,9 +51,9 @@ struct InlineList::Button { mutable std::unique_ptr custom; std::unique_ptr userpics; ReactionId id; - QString countText; + QString text; + int textWidth = 0; int count = 0; - int countTextWidth = 0; bool chosen = false; bool tag = false; }; @@ -120,20 +120,23 @@ void InlineList::layoutButtons() { return not_null{ &reaction }; }) | ranges::to_vector; const auto tags = _data.flags & Data::Flag::Tags; - const auto &list = _owner->list(::Data::Reactions::Type::All); - ranges::sort(sorted, [&]( - not_null a, - not_null b) { - const auto acount = a->count - (a->my ? 1 : 0); - const auto bcount = b->count - (b->my ? 1 : 0); - if (acount > bcount) { - return true; - } else if (acount < bcount) { - return false; - } - return ranges::find(list, a->id, &::Data::Reaction::id) - < ranges::find(list, b->id, &::Data::Reaction::id); - }); + const auto &infos = _owner->myTagsInfo(); + if (!tags) { + const auto &list = _owner->list(::Data::Reactions::Type::All); + ranges::sort(sorted, [&]( + not_null a, + not_null b) { + const auto acount = a->count - (a->my ? 1 : 0); + const auto bcount = b->count - (b->my ? 1 : 0); + if (acount > bcount) { + return true; + } else if (acount < bcount) { + return false; + } + return ranges::find(list, a->id, &::Data::Reaction::id) + < ranges::find(list, b->id, &::Data::Reaction::id); + }); + } _hasCustomEmoji = false; auto buttons = std::vector