mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Play generic animations for custom reactions.
This commit is contained in:
parent
47709884dd
commit
7d77e8a203
6 changed files with 171 additions and 42 deletions
|
@ -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({});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue