From 8bde4886620dfac4ad7dbc6bb06960d1c1f87829 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Jan 2022 15:13:49 +0300 Subject: [PATCH] Support multiple reaction animations in one message. --- .../history/view/history_view_bottom_info.cpp | 80 +++++++++++++------ .../history/view/history_view_bottom_info.h | 11 +-- .../history/view/history_view_element.cpp | 6 +- .../history/view/history_view_element.h | 4 +- .../history/view/history_view_message.cpp | 25 +++--- .../history/view/history_view_message.h | 4 +- .../view/history_view_react_animation.cpp | 8 +- .../view/history_view_react_animation.h | 3 +- .../history/view/history_view_reactions.cpp | 76 +++++++++++++----- .../history/view/history_view_reactions.h | 10 ++- 10 files changed, 149 insertions(+), 78 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 8ffb25399..d62cd8d48 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -313,19 +313,22 @@ void BottomInfo::paintReactions( int top, int availableWidth, const PaintContext &context) const { + struct SingleAnimation { + not_null animation; + QRect target; + }; + std::vector animations; + auto x = left; auto y = top; auto widthLeft = availableWidth; - const auto animated = _reactionAnimation - ? _reactionAnimation->playingAroundEmoji() - : QString(); - if (_reactionAnimation - && context.reactionInfo - && animated.isEmpty()) { - _reactionAnimation = nullptr; - } for (const auto &reaction : _reactions) { - const auto animating = (reaction.emoji == animated); + if (context.reactionInfo + && reaction.animation + && reaction.animation->finished()) { + reaction.animation = nullptr; + } + const auto animating = (reaction.animation != nullptr); const auto add = (reaction.countTextWidth > 0) ? st::reactionInfoDigitSkip : st::reactionInfoBetween; @@ -349,14 +352,15 @@ void BottomInfo::paintReactions( st::reactionInfoImage, st::reactionInfoImage); const auto skipImage = animating - && (reaction.count < 2 || !_reactionAnimation->flying()); + && (reaction.count < 2 || !reaction.animation->flying()); if (!reaction.image.isNull() && !skipImage) { p.drawImage(image.topLeft(), reaction.image); } if (animating) { - context.reactionInfo->effectPaint = [=](QPainter &p) { - return _reactionAnimation->paintGetArea(p, origin, image); - }; + animations.push_back({ + .animation = reaction.animation.get(), + .target = image, + }); } if (reaction.countTextWidth > 0) { p.drawText( @@ -367,6 +371,19 @@ void BottomInfo::paintReactions( x += width + add; widthLeft -= width + add; } + if (!animations.empty()) { + context.reactionInfo->effectPaint = [=](QPainter &p) { + auto result = QRect(); + for (const auto &single : animations) { + const auto area = single.animation->paintGetArea( + p, + origin, + single.target); + result = result.isEmpty() ? area : result.united(area); + } + return result; + }; + } } QSize BottomInfo::countCurrentSize(int newWidth) { @@ -440,11 +457,6 @@ void BottomInfo::layoutRepliesText() { } void BottomInfo::layoutReactionsText() { - if (_reactionAnimation - && !_data.reactions.contains( - _reactionAnimation->playingAroundEmoji())) { - _reactionAnimation = nullptr; - } if (_data.reactions.empty()) { _reactions.clear(); return; @@ -515,21 +527,39 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) { void BottomInfo::animateReaction( ReactionAnimationArgs &&args, Fn repaint) { - _reactionAnimation = std::make_unique( + const auto i = ranges::find(_reactions, args.emoji, &Reaction::emoji); + if (i == end(_reactions)) { + return; + } + i->animation = std::make_unique( _reactionsOwner, args.translated(QPoint(width(), height())), std::move(repaint), st::reactionInfoImage); } -auto BottomInfo::takeSendReactionAnimation() --> std::unique_ptr { - return std::move(_reactionAnimation); +auto BottomInfo::takeReactionAnimations() +-> base::flat_map> { + auto result = base::flat_map< + QString, + std::unique_ptr>(); + for (auto &reaction : _reactions) { + if (reaction.animation) { + result.emplace(reaction.emoji, std::move(reaction.animation)); + } + } + return result; } -void BottomInfo::continueSendReactionAnimation( - std::unique_ptr animation) { - _reactionAnimation = std::move(animation); +void BottomInfo::continueReactionAnimations(base::flat_map< + QString, + std::unique_ptr> animations) { + for (auto &[emoji, animation] : animations) { + const auto i = ranges::find(_reactions, emoji, &Reaction::emoji); + if (i != end(_reactions)) { + i->animation = std::move(animation); + } + } } BottomInfo::Data BottomInfoDataFromMessage(not_null message) { diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index 3e3581e9c..5266f8ae1 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -76,13 +76,15 @@ public: void animateReaction( ReactionAnimationArgs &&args, Fn repaint); - [[nodiscard]] auto takeSendReactionAnimation() - -> std::unique_ptr; - void continueSendReactionAnimation( - std::unique_ptr animation); + [[nodiscard]] auto takeReactionAnimations() + -> base::flat_map>; + void continueReactionAnimations(base::flat_map< + QString, + std::unique_ptr> animations); private: struct Reaction { + mutable std::unique_ptr animation; mutable QImage image; QString emoji; QString countText; @@ -124,7 +126,6 @@ private: Ui::Text::String _replies; std::vector _reactions; mutable ClickHandlerPtr _revokeLink; - mutable std::unique_ptr _reactionAnimation; int _reactionsMaxWidth = 0; int _dateWidth = 0; bool _authorElided = false; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 0033b1ffe..0fca73a21 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1072,9 +1072,9 @@ void Element::animateUnreadReactions() { } } -auto Element::takeSendReactionAnimation() --> std::unique_ptr { - return nullptr; +auto Element::takeReactionAnimations() +-> base::flat_map> { + return {}; } Element::~Element() { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 578b60bd7..3dafea728 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -423,8 +423,8 @@ public: virtual void animateReaction(ReactionAnimationArgs &&args); void animateUnreadReactions(); - [[nodiscard]] virtual auto takeSendReactionAnimation() - -> std::unique_ptr; + [[nodiscard]] virtual auto takeReactionAnimations() + -> base::flat_map>; virtual ~Element(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 686735fba..c53e2633a 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -253,15 +253,18 @@ Message::Message( initLogEntryOriginal(); initPsa(); refreshReactions(); - auto animation = replacing - ? replacing->takeSendReactionAnimation() - : nullptr; - if (animation) { - animation->setRepaintCallback([=] { repaint(); }); + auto animations = replacing + ? replacing->takeReactionAnimations() + : base::flat_map>(); + if (!animations.empty()) { + const auto repainter = [=] { repaint(); }; + for (const auto &[emoji, animation] : animations) { + animation->setRepaintCallback(repainter); + } if (_reactions) { - _reactions->continueSendAnimation(std::move(animation)); + _reactions->continueAnimations(std::move(animations)); } else { - _bottomInfo.continueSendReactionAnimation(std::move(animation)); + _bottomInfo.continueReactionAnimations(std::move(animations)); } } } @@ -424,11 +427,11 @@ void Message::animateReaction(ReactionAnimationArgs &&args) { } } -auto Message::takeSendReactionAnimation() --> std::unique_ptr { +auto Message::takeReactionAnimations() +-> base::flat_map> { return _reactions - ? _reactions->takeSendAnimation() - : _bottomInfo.takeSendReactionAnimation(); + ? _reactions->takeAnimations() + : _bottomInfo.takeReactionAnimations(); } QSize Message::performCountOptimalSize() { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index a9ffa7db0..c8550d23f 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -136,8 +136,8 @@ public: const base::flat_set &changes) override; void animateReaction(ReactionAnimationArgs &&args) override; - auto takeSendReactionAnimation() - -> std::unique_ptr override; + auto takeReactionAnimations() + -> base::flat_map> override; protected: void refreshDataIdHook() override; diff --git a/Telegram/SourceFiles/history/view/history_view_react_animation.cpp b/Telegram/SourceFiles/history/view/history_view_react_animation.cpp index fbf6610e1..c5804559d 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_animation.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_animation.cpp @@ -27,11 +27,10 @@ Animation::Animation( Fn repaint, int size) : _owner(owner) -, _emoji(args.emoji) , _repaint(std::move(repaint)) , _flyFrom(args.flyFrom) { const auto &list = owner->list(::Data::Reactions::Type::All); - const auto i = ranges::find(list, _emoji, &::Data::Reaction::emoji); + const auto i = ranges::find(list, args.emoji, &::Data::Reaction::emoji); if (i == end(list) || !i->centerIcon) { return; } @@ -172,10 +171,9 @@ float64 Animation::flyingProgress() const { return _fly.value(1.); } -QString Animation::playingAroundEmoji() const { - const auto finished = !_valid +bool Animation::finished() const { + return !_valid || (!_flyIcon && !_center->animating() && !_effect->animating()); - return finished ? QString() : _emoji; } } // namespace HistoryView::Reactions diff --git a/Telegram/SourceFiles/history/view/history_view_react_animation.h b/Telegram/SourceFiles/history/view/history_view_react_animation.h index d0add07b3..ba13aeda8 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_animation.h +++ b/Telegram/SourceFiles/history/view/history_view_react_animation.h @@ -35,9 +35,9 @@ public: void setRepaintCallback(Fn repaint); QRect paintGetArea(QPainter &p, QPoint origin, QRect target) const; - [[nodiscard]] QString playingAroundEmoji() const; [[nodiscard]] bool flying() const; [[nodiscard]] float64 flyingProgress() const; + [[nodiscard]] bool finished() const; private: void flyCallback(); @@ -46,7 +46,6 @@ private: int computeParabolicTop(int from, int to, float64 progress) const; const not_null<::Data::Reactions*> _owner; - const QString _emoji; Fn _repaint; std::shared_ptr _flyIcon; std::unique_ptr _center; diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/history_view_reactions.cpp index 41f0c9cef..f781166dd 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reactions.cpp @@ -268,29 +268,34 @@ void InlineList::paint( const PaintContext &context, int outerWidth, const QRect &clip) const { + struct SingleAnimation { + not_null animation; + QRect target; + }; + std::vector animations; + const auto st = context.st; const auto stm = context.messageStyle(); const auto padding = st::reactionInlinePadding; const auto size = st::reactionInlineSize; const auto skip = (size - st::reactionInlineImage) / 2; const auto inbubble = (_data.flags & InlineListData::Flag::InBubble); - const auto animated = (_animation && context.reactionInfo) - ? _animation->playingAroundEmoji() - : QString(); const auto flipped = (_data.flags & Data::Flag::Flipped); - if (_animation && context.reactionInfo && animated.isEmpty()) { - _animation = nullptr; - } p.setFont(st::semiboldFont); for (const auto &button : _buttons) { + if (context.reactionInfo + && button.animation + && button.animation->finished()) { + button.animation = nullptr; + } + const auto animating = (button.animation != nullptr); const auto &geometry = button.geometry; const auto mine = (_data.chosenReaction == button.emoji); const auto withoutMine = button.count - (mine ? 1 : 0); - const auto animating = (animated == button.emoji); const auto skipImage = animating - && (withoutMine < 1 || !_animation->flying()); + && (withoutMine < 1 || !button.animation->flying()); const auto bubbleProgress = skipImage - ? _animation->flyingProgress() + ? button.animation->flyingProgress() : 1.; const auto bubbleReady = (bubbleProgress == 1.); const auto bubbleSkip = anim::interpolate( @@ -299,7 +304,7 @@ void InlineList::paint( bubbleProgress); const auto inner = geometry.marginsRemoved(padding); const auto chosen = mine - && (!animating || !_animation->flying() || skipImage); + && (!animating || !button.animation->flying() || skipImage); if (bubbleProgress > 0.) { auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); @@ -342,9 +347,10 @@ void InlineList::paint( p.drawImage(image.topLeft(), button.image); } if (animating) { - context.reactionInfo->effectPaint = [=](QPainter &p) { - return _animation->paintGetArea(p, QPoint(), image); - }; + animations.push_back({ + .animation = button.animation.get(), + .target = image, + }); } if (bubbleProgress == 0.) { continue; @@ -381,6 +387,19 @@ void InlineList::paint( p.setOpacity(1.); } } + if (!animations.empty()) { + context.reactionInfo->effectPaint = [=](QPainter &p) { + auto result = QRect(); + for (const auto &single : animations) { + const auto area = single.animation->paintGetArea( + p, + QPoint(), + single.target); + result = result.isEmpty() ? area : result.united(area); + } + return result; + }; + } } bool InlineList::getState( @@ -411,7 +430,11 @@ bool InlineList::getState( void InlineList::animate( ReactionAnimationArgs &&args, Fn repaint) { - _animation = std::make_unique( + const auto i = ranges::find(_buttons, args.emoji, &Button::emoji); + if (i == end(_buttons)) { + return; + } + i->animation = std::make_unique( _owner, std::move(args), std::move(repaint), @@ -447,13 +470,28 @@ void InlineList::resolveUserpicsImage(const Button &button) const { kMaxRecentUserpics); } -std::unique_ptr InlineList::takeSendAnimation() { - return std::move(_animation); +auto InlineList::takeAnimations() +-> base::flat_map> { + auto result = base::flat_map< + QString, + std::unique_ptr>(); + for (auto &button : _buttons) { + if (button.animation) { + result.emplace(button.emoji, std::move(button.animation)); + } + } + return result; } -void InlineList::continueSendAnimation( - std::unique_ptr animation) { - _animation = std::move(animation); +void InlineList::continueAnimations(base::flat_map< + QString, + std::unique_ptr> animations) { + for (auto &[emoji, animation] : animations) { + const auto i = ranges::find(_buttons, emoji, &Button::emoji); + if (i != end(_buttons)) { + i->animation = std::move(animation); + } + } } InlineListData InlineListDataFromMessage(not_null message) { diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.h b/Telegram/SourceFiles/history/view/history_view_reactions.h index f73a01253..a5ebe43aa 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.h +++ b/Telegram/SourceFiles/history/view/history_view_reactions.h @@ -75,8 +75,11 @@ public: void animate( ReactionAnimationArgs &&args, Fn repaint); - [[nodiscard]] std::unique_ptr takeSendAnimation(); - void continueSendAnimation(std::unique_ptr animation); + [[nodiscard]] auto takeAnimations() + -> base::flat_map>; + void continueAnimations(base::flat_map< + QString, + std::unique_ptr> animations); private: struct Userpics { @@ -86,6 +89,7 @@ private: }; struct Button { QRect geometry; + mutable std::unique_ptr animation; mutable QImage image; mutable ClickHandlerPtr link; std::unique_ptr userpics; @@ -113,8 +117,6 @@ private: std::vector