Play generic animations for custom reactions.

This commit is contained in:
John Preston 2022-09-01 13:29:10 +04:00
parent 47709884dd
commit 7d77e8a203
6 changed files with 171 additions and 42 deletions

View file

@ -276,6 +276,39 @@ void Reactions::setFavorite(const ReactionId &id) {
applyFavorite(id);
}
DocumentData *Reactions::chooseGenericAnimation(
not_null<DocumentData*> 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<DocumentData*> 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<DocumentData*> 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({});
}

View file

@ -81,6 +81,8 @@ public:
[[nodiscard]] ReactionId favoriteId() const;
[[nodiscard]] const Reaction *favorite() const;
void setFavorite(const ReactionId &id);
[[nodiscard]] DocumentData *chooseGenericAnimation(
not_null<DocumentData*> 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<Reaction> _top;
std::vector<ReactionId> _topIds;
base::flat_set<ReactionId> _unresolvedTop;
std::vector<not_null<DocumentData*>> _genericAnimations;
ReactionId _favoriteId;
ReactionId _unresolvedFavoriteId;
std::optional<Reaction> _favorite;
base::flat_map<
not_null<DocumentData*>,
std::shared_ptr<DocumentMedia>> _iconsCache;
base::flat_map<
not_null<DocumentData*>,
std::shared_ptr<DocumentMedia>> _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<ReactionId, ImageSet> _images;
rpl::lifetime _imagesLoadLifetime;
bool _waitingForList = false;

View file

@ -562,11 +562,13 @@ auto CustomEmojiManager::createLoaderWithSetId(
SizeTag tag,
int sizeOverride
) -> LoaderWithSetId {
const auto sticker = document->sticker();
return {
std::make_unique<CustomEmojiLoader>(document, tag, sizeOverride),
sticker ? sticker->set.id : uint64()
};
if (const auto sticker = document->sticker()) {
return {
std::make_unique<CustomEmojiLoader>(document, tag, sizeOverride),
sticker->set.id,
};
}
return createLoaderWithSetId(document->id, tag, sizeOverride);
}
auto CustomEmojiManager::createLoaderWithSetId(

View file

@ -326,6 +326,7 @@ void InlineList::paint(
};
std::vector<SingleAnimation> animations;
auto finished = std::vector<std::unique_ptr<Animation>>();
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;

View file

@ -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()));
}

View file

@ -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<void()> _repaint;
QImage _flyIcon;
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
std::unique_ptr<Ui::AnimatedIcon> _center;
std::unique_ptr<Ui::AnimatedIcon> _effect;
Ui::Animations::Simple _fly;