From 7d77e8a203725e54ef71ec24cab426ec5903c60d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 1 Sep 2022 13:29:10 +0400 Subject: [PATCH] Play generic animations for custom reactions. --- .../data/data_message_reactions.cpp | 90 ++++++++++++++++++- .../SourceFiles/data/data_message_reactions.h | 10 +++ .../data/stickers/data_custom_emoji.cpp | 12 +-- .../view/reactions/history_view_reactions.cpp | 4 +- .../history_view_reactions_animation.cpp | 88 +++++++++++------- .../history_view_reactions_animation.h | 9 +- 6 files changed, 171 insertions(+), 42 deletions(-) diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 3d3a8f640..8d5bfa6f7 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -276,6 +276,39 @@ void Reactions::setFavorite(const ReactionId &id) { applyFavorite(id); } +DocumentData *Reactions::chooseGenericAnimation( + not_null custom) const { + const auto sticker = custom->sticker(); + const auto i = sticker + ? ranges::find( + _available, + ::Data::ReactionId{ { sticker->alt } }, + &::Data::Reaction::id) + : end(_available); + if (i != end(_available) && i->aroundAnimation) { + const auto view = i->aroundAnimation->createMediaView(); + view->checkStickerLarge(); + if (view->loaded()) { + return i->aroundAnimation; + } + } + if (_genericAnimations.empty()) { + return nullptr; + } + auto copy = _genericAnimations; + ranges::shuffle(copy); + const auto first = copy.front(); + const auto view = first->createMediaView(); + view->checkStickerLarge(); + if (view->loaded()) { + return first; + } + const auto k = ranges::find_if(copy, [&](not_null value) { + return value->createMediaView()->loaded(); + }); + return (k != end(copy)) ? (*k) : first; +} + void Reactions::applyFavorite(const ReactionId &id) { if (_favoriteId != id) { _favoriteId = id; @@ -324,11 +357,16 @@ void Reactions::preloadImageFor(const ReactionId &id) { } void Reactions::preloadAnimationsFor(const ReactionId &id) { - const auto i = ranges::find(_available, id, &Reaction::id); + const auto custom = id.custom(); + const auto document = custom ? _owner->document(custom).get() : nullptr; + const auto customSticker = document ? document->sticker() : nullptr; + const auto findId = custom + ? ReactionId{ { customSticker ? customSticker->alt : QString() } } + : id; + const auto i = ranges::find(_available, findId, &Reaction::id); if (i == end(_available)) { return; } - const auto preload = [&](DocumentData *document) { const auto view = document ? document->activeMediaView() @@ -337,7 +375,10 @@ void Reactions::preloadAnimationsFor(const ReactionId &id) { view->checkStickerLarge(); } }; - preload(i->centerIcon); + + if (!custom) { + preload(i->centerIcon); + } preload(i->aroundAnimation); } @@ -516,6 +557,26 @@ void Reactions::requestDefault() { }).send(); } +void Reactions::requestGeneric() { + if (_genericRequestId) { + return; + } + auto &api = _owner->session().api(); + _genericRequestId = api.request(MTPmessages_GetStickerSet( + MTP_inputStickerSetEmojiGenericAnimations(), + MTP_int(0) // hash + )).done([=](const MTPmessages_StickerSet &result) { + _genericRequestId = 0; + result.match([&](const MTPDmessages_stickerSet &data) { + updateGeneric(data); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); + }).fail([=] { + _genericRequestId = 0; + }).send(); +} + void Reactions::updateTop(const MTPDmessages_reactions &data) { _topHash = data.vhash().v; _topIds = ListFromMTP(data); @@ -564,6 +625,26 @@ void Reactions::updateDefault(const MTPDmessages_availableReactions &data) { defaultUpdated(); } +void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) { + const auto oldCache = base::take(_genericCache); + const auto toCache = [&](not_null document) { + if (document->sticker()) { + _genericAnimations.push_back(document); + _genericCache.emplace(document, document->createMediaView()); + } + }; + const auto &list = data.vdocuments().v; + _genericAnimations.clear(); + _genericAnimations.reserve(list.size()); + _genericCache.reserve(list.size()); + for (const auto &sticker : data.vdocuments().v) { + toCache(_owner->processDocument(sticker)); + } + if (!_genericCache.empty()) { + _genericCache.front().second->checkStickerLarge(); + } +} + void Reactions::recentUpdated() { _topRefreshTimer.callOnce(kTopRequestDelay); _recentUpdated.fire({}); @@ -572,6 +653,9 @@ void Reactions::recentUpdated() { void Reactions::defaultUpdated() { refreshTop(); refreshRecent(); + if (_genericAnimations.empty()) { + requestGeneric(); + } _defaultUpdated.fire({}); } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index abe178078..c1aeb6f73 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -81,6 +81,8 @@ public: [[nodiscard]] ReactionId favoriteId() const; [[nodiscard]] const Reaction *favorite() const; void setFavorite(const ReactionId &id); + [[nodiscard]] DocumentData *chooseGenericAnimation( + not_null custom) const; [[nodiscard]] rpl::producer<> topUpdates() const; [[nodiscard]] rpl::producer<> recentUpdates() const; @@ -127,10 +129,12 @@ private: void requestTop(); void requestRecent(); void requestDefault(); + void requestGeneric(); void updateTop(const MTPDmessages_reactions &data); void updateRecent(const MTPDmessages_reactions &data); void updateDefault(const MTPDmessages_availableReactions &data); + void updateGeneric(const MTPDmessages_stickerSet &data); void recentUpdated(); void defaultUpdated(); @@ -166,12 +170,16 @@ private: std::vector _top; std::vector _topIds; base::flat_set _unresolvedTop; + std::vector> _genericAnimations; ReactionId _favoriteId; ReactionId _unresolvedFavoriteId; std::optional _favorite; base::flat_map< not_null, std::shared_ptr> _iconsCache; + base::flat_map< + not_null, + std::shared_ptr> _genericCache; rpl::event_stream<> _topUpdated; rpl::event_stream<> _recentUpdated; rpl::event_stream<> _defaultUpdated; @@ -193,6 +201,8 @@ private: mtpRequestId _defaultRequestId = 0; int32 _defaultHash = 0; + mtpRequestId _genericRequestId = 0; + base::flat_map _images; rpl::lifetime _imagesLoadLifetime; bool _waitingForList = false; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 3aea27fd2..19e10d394 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -562,11 +562,13 @@ auto CustomEmojiManager::createLoaderWithSetId( SizeTag tag, int sizeOverride ) -> LoaderWithSetId { - const auto sticker = document->sticker(); - return { - std::make_unique(document, tag, sizeOverride), - sticker ? sticker->set.id : uint64() - }; + if (const auto sticker = document->sticker()) { + return { + std::make_unique(document, tag, sizeOverride), + sticker->set.id, + }; + } + return createLoaderWithSetId(document->id, tag, sizeOverride); } auto CustomEmojiManager::createLoaderWithSetId( diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index 4d9ef22ce..1067a9d77 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -326,6 +326,7 @@ void InlineList::paint( }; std::vector animations; + auto finished = std::vector>(); const auto st = context.st; const auto stm = context.messageStyle(); const auto padding = st::reactionInlinePadding; @@ -338,7 +339,8 @@ void InlineList::paint( if (context.reactionInfo && button.animation && button.animation->finished()) { - button.animation = nullptr; + // Let the animation (and its custom emoji) live while painting. + finished.push_back(std::move(button.animation)); } const auto animating = (button.animation != nullptr); const auto &geometry = button.geometry; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp index 77f6802b0..3801e75f7 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp @@ -24,6 +24,26 @@ constexpr auto kFlyDuration = crl::time(300); } // namespace +auto Animation::flyCallback() { + return [=] { + if (!_fly.animating()) { + _flyIcon = QImage(); + startAnimations(); + } + if (_repaint) { + _repaint(); + } + }; +} + +auto Animation::callback() { + return [=] { + if (_repaint) { + _repaint(); + } + }; +} + Animation::Animation( not_null<::Data::Reactions*> owner, ReactionAnimationArgs &&args, @@ -37,11 +57,11 @@ Animation::Animation( auto centerIconSize = size; auto aroundAnimation = (DocumentData*)nullptr; if (const auto customId = args.id.custom()) { - const auto document = owner->owner().document(customId); - if (document->sticker()) { - centerIcon = document; - centerIconSize = Ui::Text::AdjustCustomEmojiSize(st::emojiSize); - } + centerIconSize = Ui::Text::AdjustCustomEmojiSize(st::emojiSize); + const auto data = &owner->owner(); + const auto document = data->document(customId); + _custom = data->customEmojiManager().create(document, callback()); + aroundAnimation = owner->chooseGenericAnimation(document); } else { const auto i = ranges::find(list, args.id, &::Data::Reaction::id); if (i == end(list) || !i->centerIcon) { @@ -67,17 +87,19 @@ Animation::Animation( }); return true; }; - _flyIcon = std::move(args.flyIcon); - _centerSizeMultiplier = centerIconSize / float64(size); - if (!resolve(_center, centerIcon, centerIconSize)) { + if (!_custom && !resolve(_center, centerIcon, centerIconSize)) { return; } resolve(_effect, aroundAnimation, size * 2); - if (!_flyIcon.isNull()) { - _fly.start([=] { flyCallback(); }, 0., 1., kFlyDuration); + if (!args.flyIcon.isNull()) { + _flyIcon = std::move(args.flyIcon); + _fly.start(flyCallback(), 0., 1., kFlyDuration); + } else if (!_center && !_effect) { + return; } else { startAnimations(); } + _centerSizeMultiplier = centerIconSize / float64(size); _valid = true; } @@ -122,16 +144,32 @@ QRect Animation::paintGetArea( } void Animation::paintCenterFrame(QPainter &p, QRect target) const { + Expects(_center || _custom); + const auto size = QSize( int(base::SafeRound(target.width() * _centerSizeMultiplier)), int(base::SafeRound(target.height() * _centerSizeMultiplier))); - p.drawImage( - QRect( + if (_center) { + const auto rect = QRect( target.x() + (target.width() - size.width()) / 2, target.y() + (target.height() - size.height()) / 2, size.width(), - size.height()), - _center->frame()); + size.height()); + p.drawImage(rect, _center->frame()); + } else { + const auto side = Ui::Text::AdjustCustomEmojiSize(st::emojiSize); + const auto scaled = (size.width() != side); + _custom->paint(p, { + .preview = Qt::transparent, + .size = { side, side }, + .now = crl::now(), + .scale = (scaled ? (size.width() / float64(side)) : 1.), + .position = QPoint( + target.x() + (target.width() - side) / 2, + target.y() + (target.height() - side) / 2), + .scaled = scaled, + }); + } } int Animation::computeParabolicTop( @@ -171,23 +209,11 @@ int Animation::computeParabolicTop( } void Animation::startAnimations() { - _center->animate([=] { callback(); }); + if (const auto center = _center.get()) { + _center->animate(callback()); + } if (const auto effect = _effect.get()) { - _effect->animate([=] { callback(); }); - } -} - -void Animation::flyCallback() { - if (!_fly.animating()) { - _flyIcon = QImage(); - startAnimations(); - } - callback(); -} - -void Animation::callback() { - if (_repaint) { - _repaint(); + _effect->animate(callback()); } } @@ -206,7 +232,7 @@ float64 Animation::flyingProgress() const { bool Animation::finished() const { return !_valid || (_flyIcon.isNull() - && !_center->animating() + && (!_center || !_center->animating()) && (!_effect || !_effect->animating())); } diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h index 92e236943..df7382810 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h @@ -13,6 +13,10 @@ namespace Ui { class AnimatedIcon; } // namespace Lottie +namespace Ui::Text { +class CustomEmoji; +} // namespace Ui::Text + namespace Data { class Reactions; } // namespace Data @@ -40,15 +44,16 @@ public: [[nodiscard]] bool finished() const; private: - void flyCallback(); + [[nodiscard]] auto flyCallback(); + [[nodiscard]] auto callback(); void startAnimations(); - void callback(); int computeParabolicTop(int from, int to, float64 progress) const; void paintCenterFrame(QPainter &p, QRect target) const; const not_null<::Data::Reactions*> _owner; Fn _repaint; QImage _flyIcon; + std::unique_ptr _custom; std::unique_ptr _center; std::unique_ptr _effect; Ui::Animations::Simple _fly;