From c25adf8b57147759f4b78678c4c9d8bf9a5b5a4f Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 13 Jan 2025 17:41:16 +0400 Subject: [PATCH] Use a custom animated emoji for collectible status. --- .../boxes/peers/edit_peer_color_box.cpp | 8 +- .../chat_helpers/emoji_list_widget.cpp | 82 +++++++++---------- .../chat_helpers/emoji_list_widget.h | 9 +- .../SourceFiles/data/data_emoji_statuses.cpp | 5 ++ .../SourceFiles/data/data_emoji_statuses.h | 1 + .../data/stickers/data_custom_emoji.cpp | 30 +++++++ .../data/stickers/data_custom_emoji.h | 4 + .../view/history_view_contact_status.cpp | 10 +-- .../history/view/history_view_message.cpp | 10 +-- .../info/profile/info_profile_badge.cpp | 7 +- .../ui/effects/premium_stars_colored.cpp | 82 ++++++++++++++++++- .../ui/effects/premium_stars_colored.h | 20 ++++- Telegram/SourceFiles/ui/unread_badge.cpp | 16 ++-- 13 files changed, 195 insertions(+), 89 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 6cd4eb607..db590a10e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -935,13 +935,9 @@ int ColorSelector::resizeGetHeight(int newWidth) { const auto session = &show->session(); std::move(statusIdValue) | rpl::start_with_next([=](EmojiStatusId id) { state->statusId = id; - state->emoji = id.collectible // todo collectibles + state->emoji = id ? session->data().customEmojiManager().create( - id.collectible->documentId, - [=] { right->update(); }) - : id.documentId - ? session->data().customEmojiManager().create( - id.documentId, + Data::EmojiStatusCustomId(id), [=] { right->update(); }) : nullptr; right->resize( diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 26c650bba..d719cc2e8 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -1129,17 +1129,16 @@ void EmojiListWidget::fillRecentFrom( }); _recentCustomIds.emplace(fakeId); } else { - const auto documentId = id.collectible - ? id.collectible->documentId - : id.documentId; _recent.push_back({ .collectible = id.collectible, - .custom = resolveCustomRecent(documentId), + .custom = resolveCustomRecent(id), .id = { - RecentEmojiDocument{ .id = documentId, .test = test }, + RecentEmojiDocument{ .id = id.documentId, .test = test }, }, }); - _recentCustomIds.emplace(documentId); + _recentCustomIds.emplace(id.collectible + ? id.collectible->documentId + : id.documentId); } } } @@ -1490,27 +1489,6 @@ void EmojiListWidget::drawCollapsedBadge( text); } -void EmojiListWidget::drawCollectible( - QPainter &p, - QPoint position, - Data::EmojiStatusCollectible *collectible) { - if (!collectible) { - return; - } - const auto inner = QRect(position, st::emojiPanArea); - auto gradient = QRadialGradient(inner.center(), inner.height() / 2); - gradient.setStops({ - { 0., collectible->centerColor }, - { 1., collectible->edgeColor }, - }); - p.setBrush(gradient); - p.setPen(Qt::NoPen); - p.drawRoundedRect( - inner, - st::emojiPanRadius, - st::emojiPanRadius); -} - void EmojiListWidget::drawRecent( QPainter &p, const ExpandingContext &context, @@ -1547,14 +1525,12 @@ void EmojiListWidget::drawRecent( auto q = Painter(&_premiumMarkFrameCache); _emojiPaintContext->position = QPoint(); - drawCollectible(q, position, recent.collectible.get()); custom->paint(q, *_emojiPaintContext); q.end(); p.drawImage(exactPosition, _premiumMarkFrameCache); } else { _emojiPaintContext->position = exactPosition; - drawCollectible(p, position, recent.collectible.get()); custom->paint(p, *_emojiPaintContext); } } else if (const auto emoji = std::get_if(&recent.id.data)) { @@ -1609,7 +1585,6 @@ void EmojiListWidget::drawCustom( _emojiPaintContext->position = position + _innerPosition + _customPosition; - drawCollectible(p, position, entry.collectible.get()); entry.custom->paint(p, *_emojiPaintContext); } @@ -1643,8 +1618,15 @@ EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji( } return {}; } else if (section == int(Section::Recent) && index < _recent.size()) { + const auto &recent = _recent[index]; + if (recent.collectible) { + return { + session().data().document(recent.collectible->documentId), + recent.collectible, + }; + } const auto document = std::get_if( - &_recent[index].id.data); + &recent.id.data); if (document) { return { session().data().document(document->id) }; } @@ -2342,11 +2324,12 @@ void EmojiListWidget::refreshCustom() { auto set = std::vector(); set.reserve(list.size()); for (const auto document : list) { - if (_restrictedCustomList.contains(document->id)) { + const auto id = EmojiStatusId{ document->id }; + if (_restrictedCustomList.contains(id.documentId)) { continue; } else if (const auto sticker = document->sticker()) { set.push_back({ - .custom = resolveCustomEmoji(document, lookupId), + .custom = resolveCustomEmoji(id, document, lookupId), .document = document, .emoji = Ui::Emoji::Find(sticker->alt), }); @@ -2401,18 +2384,19 @@ Fn EmojiListWidget::repaintCallback( } not_null EmojiListWidget::resolveCustomEmoji( + EmojiStatusId id, not_null document, uint64 setId) { Expects(document->sticker() != nullptr); const auto documentId = document->id; - const auto i = _customEmoji.find(documentId); + const auto i = _customEmoji.find(id); const auto recentOnly = (i != end(_customEmoji)) && i->second.recentOnly; if (i != end(_customEmoji) && !recentOnly) { return i->second.emoji.get(); } auto instance = document->owner().customEmojiManager().create( - document, + Data::EmojiStatusCustomId(id), repaintCallback(documentId, setId), Data::CustomEmojiManager::SizeTag::Large); if (recentOnly) { @@ -2426,7 +2410,7 @@ not_null EmojiListWidget::resolveCustomEmoji( return i->second.emoji.get(); } return _customEmoji.emplace( - documentId, + id, CustomEmojiInstance{ .emoji = std::move(instance) } ).first->second.emoji.get(); } @@ -2444,27 +2428,37 @@ Ui::Text::CustomEmoji *EmojiListWidget::resolveCustomRecent( not_null EmojiListWidget::resolveCustomRecent( DocumentId documentId) { - const auto i = _customRecent.find(documentId); + return resolveCustomRecent(EmojiStatusId{ documentId }); +} + +not_null EmojiListWidget::resolveCustomRecent( + EmojiStatusId id) { + const auto i = id.collectible + ? end(_customRecent) + : _customRecent.find(id.documentId); if (i != end(_customRecent)) { return i->second.get(); } - const auto j = _customEmoji.find(documentId); + const auto j = _customEmoji.find(id); if (j != end(_customEmoji)) { return j->second.emoji.get(); } + const auto documentId = id.collectible + ? id.collectible->documentId + : id.documentId; auto repaint = repaintCallback(documentId, RecentEmojiSectionSetId()); - if (_customRecentFactory) { + if (_customRecentFactory && !id.collectible) { return _customRecent.emplace( - documentId, - _customRecentFactory(documentId, std::move(repaint)) + id.documentId, + _customRecentFactory(id.documentId, std::move(repaint)) ).first->second.get(); } auto custom = session().data().customEmojiManager().create( - documentId, + Data::EmojiStatusCustomId(id), std::move(repaint), Data::CustomEmojiManager::SizeTag::Large); return _customEmoji.emplace( - documentId, + id, CustomEmojiInstance{ .emoji = std::move(custom), .recentOnly = true } ).first->second.emoji.get(); } @@ -2489,7 +2483,7 @@ void EmojiListWidget::refreshEmojiStatusCollectibles() { if (const auto sticker = document->sticker()) { set.push_back({ .collectible = status.collectible, - .custom = resolveCustomEmoji(document, setId), + .custom = resolveCustomEmoji(status, document, setId), .document = document, .emoji = Ui::Emoji::Find(sticker->alt), }); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 4027ff683..fd1a1f390 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -325,10 +325,6 @@ private: void selectCustom(FileChosen data); void paint(Painter &p, ExpandingContext context, QRect clip); void drawCollapsedBadge(QPainter &p, QPoint position, int count); - void drawCollectible( - QPainter &p, - QPoint position, - Data::EmojiStatusCollectible *collectible); void drawRecent( QPainter &p, const ExpandingContext &context, @@ -389,12 +385,15 @@ private: void fillRecent(); void fillRecentFrom(const std::vector &list); [[nodiscard]] not_null resolveCustomEmoji( + EmojiStatusId id, not_null document, uint64 setId); [[nodiscard]] Ui::Text::CustomEmoji *resolveCustomRecent( Core::RecentEmojiId customId); [[nodiscard]] not_null resolveCustomRecent( DocumentId documentId); + [[nodiscard]] not_null resolveCustomRecent( + EmojiStatusId id); [[nodiscard]] Fn repaintCallback( DocumentId documentId, uint64 setId); @@ -432,7 +431,7 @@ private: QVector _emoji[kEmojiSectionCount]; std::vector _custom; base::flat_set _restrictedCustomList; - base::flat_map _customEmoji; + base::flat_map _customEmoji; base::flat_map< DocumentId, std::unique_ptr> _customRecent; diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.cpp b/Telegram/SourceFiles/data/data_emoji_statuses.cpp index 9b59d975e..73c159830 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp +++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp @@ -559,4 +559,9 @@ EmojiStatusId EmojiStatuses::fromUniqueGift( return { .collectible = collectible }; } +EmojiStatusCollectible *EmojiStatuses::collectibleInfo(CollectibleId id) { + const auto i = _collectibleData.find(id); + return (i != end(_collectibleData)) ? i->second.get() : nullptr; +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h index 70ce4903d..f7809b586 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.h +++ b/Telegram/SourceFiles/data/data_emoji_statuses.h @@ -81,6 +81,7 @@ public: void set(EmojiStatusId id, TimeId until = 0); void set(not_null peer, EmojiStatusId id, TimeId until = 0); [[nodiscard]] EmojiStatusId fromUniqueGift(const Data::UniqueGift &gift); + [[nodiscard]] EmojiStatusCollectible *collectibleInfo(CollectibleId id); void registerAutomaticClear(not_null peer, TimeId until); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index d372674f5..695cb06ee 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_emoji_statuses.h" #include "data/data_file_origin.h" #include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity. #include "data/data_peer.h" @@ -28,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers_lottie.h" #include "storage/file_download.h" // kMaxFileInMemory #include "ui/chat/chats_filter_tag.h" +#include "ui/effects/premium_stars_colored.h" #include "ui/effects/credits_graphics.h" #include "ui/widgets/fields/input_field.h" #include "ui/text/custom_emoji_instance.h" @@ -117,6 +119,10 @@ private: return u"force-static:"_q; } +[[nodiscard]] QString CollectiblePrefix() { + return u"collectible:"_q; +} + [[nodiscard]] QString InternalPadding(QMargins value) { return value.isNull() ? QString() : QString(",%1,%2,%3,%4" ).arg(value.left() @@ -568,6 +574,20 @@ std::unique_ptr CustomEmojiManager::create( const auto ratio = style::DevicePixelRatio(); const auto size = EmojiSizeFromTag(tag) / ratio; return userpic(data, std::move(update), size); + } else if (data.startsWith(CollectiblePrefix())) { + const auto id = data.mid(CollectiblePrefix().size()).toULongLong(); + const auto emojiStatuses = &session().data().emojiStatuses(); + auto info = emojiStatuses->collectibleInfo(id); + Assert(info != nullptr); + const auto documentId = info->documentId; + auto inner = create(documentId, base::duplicate(update), tag); + return Ui::Premium::MakeCollectibleEmoji( + data, + info->centerColor, + info->edgeColor, + std::move(inner), + std::move(update), + FrameSizeFromTag(tag) / style::DevicePixelRatio()); } else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) { return MakeTopicIconEmoji(parsed, std::move(update), tag); } @@ -1147,4 +1167,14 @@ Ui::Text::CustomEmojiFactory ReactedMenuFactory( }; } +QString CollectibleCustomEmojiId(Data::EmojiStatusCollectible &data) { + return CollectiblePrefix() + QString::number(data.id); +} + +QString EmojiStatusCustomId(const EmojiStatusId &id) { + return id.collectible + ? CollectibleCustomEmojiId(*id.collectible) + : SerializeCustomEmojiId(id.documentId); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 1d2f7940b..60fd75f06 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -223,4 +223,8 @@ void InsertCustomEmoji( [[nodiscard]] Ui::Text::CustomEmojiFactory ReactedMenuFactory( not_null session); +[[nodiscard]] QString CollectibleCustomEmojiId( + Data::EmojiStatusCollectible &data); +[[nodiscard]] QString EmojiStatusCustomId(const EmojiStatusId &id); + } // namespace Data diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index b4d102845..a25ebeed0 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -113,11 +113,11 @@ namespace { Data::PeerUpdate::Flag::EmojiStatus ) | rpl::map([=] { const auto id = peer->emojiStatusId(); - const auto documentId = id.collectible - ? id.collectible->documentId - : id.documentId; - return documentId - ? ResolveIsCustom(owner, documentId) + return id.collectible + ? rpl::single(Ui::Text::SingleCustomEmoji( + Data::EmojiStatusCustomId(id))) + : id.documentId + ? ResolveIsCustom(owner, id.documentId) : rpl::single(TextWithEntities()); }) | rpl::flatten_latest() | rpl::distinct_until_changed(); } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index ab9ea4888..c28069331 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -371,7 +371,7 @@ struct Message::CommentsButton { }; struct Message::FromNameStatus { - DocumentId id = 0; + EmojiStatusId id; std::unique_ptr custom; int skip = 0; }; @@ -1767,14 +1767,14 @@ void Message::paintFromName( + std::min(availableWidth - statusWidth, nameText->maxWidth()); const auto y = trect.top(); auto color = nameFg; - color.setAlpha(115); // todo collectibles - const auto id = from ? from->emojiStatusId().documentId : 0; + color.setAlpha(115); + const auto id = from ? from->emojiStatusId() : EmojiStatusId(); if (_fromNameStatus->id != id) { const auto that = const_cast(this); _fromNameStatus->custom = id ? std::make_unique( history()->owner().customEmojiManager().create( - id, + Data::EmojiStatusCustomId(id), [=] { that->customEmojiRepaint(); }), kPlayStatusLimit) : nullptr; @@ -2379,7 +2379,7 @@ void Message::unloadHeavyPart() { _comments = nullptr; if (_fromNameStatus) { _fromNameStatus->custom = nullptr; - _fromNameStatus->id = 0; + _fromNameStatus->id = EmojiStatusId(); } } diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp index 9a0452950..1bc2cbdd7 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp @@ -130,12 +130,9 @@ void Badge::setContent(Content content) { : id ? nullptr : &_st.premium; - const auto documentId = id.collectible - ? id.collectible->documentId - : id.documentId; - if (documentId) { + if (id) { _emojiStatus = _session->data().customEmojiManager().create( - documentId, + Data::EmojiStatusCustomId(id), [raw = _view.data()] { raw->update(); }, sizeTag()); if (_customStatusLoopsLimit > 0) { diff --git a/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp b/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp index c90dc97ab..2aeb1b998 100644 --- a/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp @@ -8,10 +8,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/premium_stars_colored.h" #include "ui/effects/premium_graphics.h" // GiftGradientStops. +#include "ui/text/text_custom_emoji.h" #include "ui/rp_widget.h" -namespace Ui { -namespace Premium { +namespace Ui::Premium { ColoredMiniStars::ColoredMiniStars( not_null parent, @@ -106,5 +106,79 @@ void ColoredMiniStars::setCenter(const QRect &rect) { setSize(ministarsRect.size()); } -} // namespace Premium -} // namespace Ui +std::unique_ptr MakeCollectibleEmoji( + QStringView entityData, + QColor centerColor, + QColor edgeColor, + std::unique_ptr inner, + Fn update, + int size) { + class Emoji final : public Text::CustomEmoji { + public: + Emoji( + QStringView entityData, + QColor centerColor, + QColor edgeColor, + std::unique_ptr inner, + Fn update, + int size) + : _entityData(entityData.toString()) + , _stars([=](QRect) { update(); }, MiniStars::Type::SlowStars) + , _centerColor(centerColor) + , _edgeColor(edgeColor) + , _inner(std::move(inner)) + , _size(size) { + _stars.setColorOverride(QGradientStops{ + { 0., edgeColor }, + { 1., centerColor }, + }); + _stars.setSize(QSize(size, size)); + } + + int width() override { + return _inner->width(); + } + + QString entityData() override { + return _entityData; + } + + void paint(QPainter &p, const Context &context) override { + _stars.setPosition(context.position); + _stars.paint(p); + + _inner->paint(p, context); + } + + void unload() override { + _inner->unload(); + } + + bool ready() override { + return _inner->ready(); + } + + bool readyInDefaultState() override { + return _inner->readyInDefaultState(); + } + + private: + QString _entityData; + ColoredMiniStars _stars; + QColor _centerColor; + QColor _edgeColor; + std::unique_ptr _inner; + int _size = 0; + + }; + + return std::make_unique( + entityData, + centerColor, + edgeColor, + std::move(inner), + std::move(update), + size); +} + +} // namespace Ui::Premium diff --git a/Telegram/SourceFiles/ui/effects/premium_stars_colored.h b/Telegram/SourceFiles/ui/effects/premium_stars_colored.h index 2b56d93aa..0e53869e7 100644 --- a/Telegram/SourceFiles/ui/effects/premium_stars_colored.h +++ b/Telegram/SourceFiles/ui/effects/premium_stars_colored.h @@ -11,8 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { class RpWidget; +} // namespace Ui -namespace Premium { +namespace Ui::Text { +class CustomEmoji; +} // namespace Ui::Text + +namespace Ui::Premium { class ColoredMiniStars final { public: @@ -32,7 +37,7 @@ public: void setPaused(bool paused); private: - Ui::Premium::MiniStars _ministars; + MiniStars _ministars; QRectF _ministarsRect; QImage _frame; QImage _mask; @@ -42,5 +47,12 @@ private: }; -} // namespace Premium -} // namespace Ui +[[nodiscard]] std::unique_ptr MakeCollectibleEmoji( + QStringView entityData, + QColor centerColor, + QColor edgeColor, + std::unique_ptr inner, + Fn update, + int size); + +} // namespace Ui::Premium diff --git a/Telegram/SourceFiles/ui/unread_badge.cpp b/Telegram/SourceFiles/ui/unread_badge.cpp index 1367e6e95..b01dcb198 100644 --- a/Telegram/SourceFiles/ui/unread_badge.cpp +++ b/Telegram/SourceFiles/ui/unread_badge.cpp @@ -239,17 +239,11 @@ int PeerBadge::drawPremiumEmojiStatus( using namespace Ui::Text; auto &manager = peer->session().data().customEmojiManager(); _emojiStatus->id = id; - _emojiStatus->emoji = id.collectible // todo collectibles - ? std::make_unique( - manager.create( - id.collectible->documentId, - descriptor.customEmojiRepaint), - kPlayStatusLimit) - : std::make_unique( - manager.create( - id.documentId, - descriptor.customEmojiRepaint), - kPlayStatusLimit); + _emojiStatus->emoji = std::make_unique( + manager.create( + Data::EmojiStatusCustomId(id), + descriptor.customEmojiRepaint), + kPlayStatusLimit); } _emojiStatus->emoji->paint(p, { .textColor = (*descriptor.premiumFg)->c,