Support multiple reaction animations in one message.

This commit is contained in:
John Preston 2022-01-28 15:13:49 +03:00
parent 4f1e04cf9e
commit 8bde488662
10 changed files with 149 additions and 78 deletions

View file

@ -313,19 +313,22 @@ void BottomInfo::paintReactions(
int top,
int availableWidth,
const PaintContext &context) const {
struct SingleAnimation {
not_null<Reactions::Animation*> animation;
QRect target;
};
std::vector<SingleAnimation> 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<void()> repaint) {
_reactionAnimation = std::make_unique<Reactions::Animation>(
const auto i = ranges::find(_reactions, args.emoji, &Reaction::emoji);
if (i == end(_reactions)) {
return;
}
i->animation = std::make_unique<Reactions::Animation>(
_reactionsOwner,
args.translated(QPoint(width(), height())),
std::move(repaint),
st::reactionInfoImage);
}
auto BottomInfo::takeSendReactionAnimation()
-> std::unique_ptr<Reactions::Animation> {
return std::move(_reactionAnimation);
auto BottomInfo::takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> {
auto result = base::flat_map<
QString,
std::unique_ptr<Reactions::Animation>>();
for (auto &reaction : _reactions) {
if (reaction.animation) {
result.emplace(reaction.emoji, std::move(reaction.animation));
}
}
return result;
}
void BottomInfo::continueSendReactionAnimation(
std::unique_ptr<Reactions::Animation> animation) {
_reactionAnimation = std::move(animation);
void BottomInfo::continueReactionAnimations(base::flat_map<
QString,
std::unique_ptr<Reactions::Animation>> 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*> message) {

View file

@ -76,13 +76,15 @@ public:
void animateReaction(
ReactionAnimationArgs &&args,
Fn<void()> repaint);
[[nodiscard]] auto takeSendReactionAnimation()
-> std::unique_ptr<Reactions::Animation>;
void continueSendReactionAnimation(
std::unique_ptr<Reactions::Animation> animation);
[[nodiscard]] auto takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>>;
void continueReactionAnimations(base::flat_map<
QString,
std::unique_ptr<Reactions::Animation>> animations);
private:
struct Reaction {
mutable std::unique_ptr<Reactions::Animation> animation;
mutable QImage image;
QString emoji;
QString countText;
@ -124,7 +126,6 @@ private:
Ui::Text::String _replies;
std::vector<Reaction> _reactions;
mutable ClickHandlerPtr _revokeLink;
mutable std::unique_ptr<Reactions::Animation> _reactionAnimation;
int _reactionsMaxWidth = 0;
int _dateWidth = 0;
bool _authorElided = false;

View file

@ -1072,9 +1072,9 @@ void Element::animateUnreadReactions() {
}
}
auto Element::takeSendReactionAnimation()
-> std::unique_ptr<Reactions::Animation> {
return nullptr;
auto Element::takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> {
return {};
}
Element::~Element() {

View file

@ -423,8 +423,8 @@ public:
virtual void animateReaction(ReactionAnimationArgs &&args);
void animateUnreadReactions();
[[nodiscard]] virtual auto takeSendReactionAnimation()
-> std::unique_ptr<Reactions::Animation>;
[[nodiscard]] virtual auto takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>>;
virtual ~Element();

View file

@ -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<QString, std::unique_ptr<Reactions::Animation>>();
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<Reactions::Animation> {
auto Message::takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> {
return _reactions
? _reactions->takeSendAnimation()
: _bottomInfo.takeSendReactionAnimation();
? _reactions->takeAnimations()
: _bottomInfo.takeReactionAnimations();
}
QSize Message::performCountOptimalSize() {

View file

@ -136,8 +136,8 @@ public:
const base::flat_set<UserId> &changes) override;
void animateReaction(ReactionAnimationArgs &&args) override;
auto takeSendReactionAnimation()
-> std::unique_ptr<Reactions::Animation> override;
auto takeReactionAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> override;
protected:
void refreshDataIdHook() override;

View file

@ -27,11 +27,10 @@ Animation::Animation(
Fn<void()> 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

View file

@ -35,9 +35,9 @@ public:
void setRepaintCallback(Fn<void()> 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<void()> _repaint;
std::shared_ptr<Lottie::Icon> _flyIcon;
std::unique_ptr<Lottie::Icon> _center;

View file

@ -268,29 +268,34 @@ void InlineList::paint(
const PaintContext &context,
int outerWidth,
const QRect &clip) const {
struct SingleAnimation {
not_null<Reactions::Animation*> animation;
QRect target;
};
std::vector<SingleAnimation> 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<void()> repaint) {
_animation = std::make_unique<Reactions::Animation>(
const auto i = ranges::find(_buttons, args.emoji, &Button::emoji);
if (i == end(_buttons)) {
return;
}
i->animation = std::make_unique<Reactions::Animation>(
_owner,
std::move(args),
std::move(repaint),
@ -447,13 +470,28 @@ void InlineList::resolveUserpicsImage(const Button &button) const {
kMaxRecentUserpics);
}
std::unique_ptr<Animation> InlineList::takeSendAnimation() {
return std::move(_animation);
auto InlineList::takeAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>> {
auto result = base::flat_map<
QString,
std::unique_ptr<Reactions::Animation>>();
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) {
_animation = std::move(animation);
void InlineList::continueAnimations(base::flat_map<
QString,
std::unique_ptr<Reactions::Animation>> 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*> message) {

View file

@ -75,8 +75,11 @@ public:
void animate(
ReactionAnimationArgs &&args,
Fn<void()> repaint);
[[nodiscard]] std::unique_ptr<Animation> takeSendAnimation();
void continueSendAnimation(std::unique_ptr<Animation> animation);
[[nodiscard]] auto takeAnimations()
-> base::flat_map<QString, std::unique_ptr<Reactions::Animation>>;
void continueAnimations(base::flat_map<
QString,
std::unique_ptr<Reactions::Animation>> animations);
private:
struct Userpics {
@ -86,6 +89,7 @@ private:
};
struct Button {
QRect geometry;
mutable std::unique_ptr<Animation> animation;
mutable QImage image;
mutable ClickHandlerPtr link;
std::unique_ptr<Userpics> userpics;
@ -113,8 +117,6 @@ private:
std::vector<Button> _buttons;
QSize _skipBlock;
mutable std::unique_ptr<Animation> _animation;
};
[[nodiscard]] InlineListData InlineListDataFromMessage(