mirror of
https://github.com/kotatogram/kotatogram-desktop
synced 2025-08-31 14:45:14 +00:00
Implement animated stickerset thumbnails.
This commit is contained in:
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
@@ -64,6 +65,7 @@ struct StickerIcon {
|
||||
}
|
||||
uint64 setId = 0;
|
||||
ImagePtr thumbnail;
|
||||
mutable Lottie::SinglePlayer *lottie = nullptr;
|
||||
DocumentData *sticker = nullptr;
|
||||
ChannelData *megagroup = nullptr;
|
||||
int pixw = 0;
|
||||
@@ -71,9 +73,11 @@ struct StickerIcon {
|
||||
|
||||
};
|
||||
|
||||
class StickersListWidget::Footer : public TabbedSelector::InnerFooter, private base::Subscriber {
|
||||
class StickersListWidget::Footer
|
||||
: public TabbedSelector::InnerFooter
|
||||
, private base::Subscriber {
|
||||
public:
|
||||
Footer(not_null<StickersListWidget*> parent);
|
||||
explicit Footer(not_null<StickersListWidget*> parent);
|
||||
|
||||
void preloadImages();
|
||||
void validateSelectedIcon(
|
||||
@@ -88,6 +92,8 @@ public:
|
||||
void returnFocus();
|
||||
void setLoading(bool loading);
|
||||
|
||||
void clearLottieData();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
@@ -106,6 +112,12 @@ private:
|
||||
};
|
||||
using OverState = base::variant<SpecialOver, int>;
|
||||
|
||||
struct LottieIcon {
|
||||
std::unique_ptr<Lottie::SinglePlayer> player;
|
||||
bool stale = false;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
template <typename Callback>
|
||||
void enumerateVisibleIcons(Callback callback);
|
||||
|
||||
@@ -113,9 +125,13 @@ private:
|
||||
void setSelectedIcon(
|
||||
int newSelected,
|
||||
ValidateIconAnimations animations);
|
||||
void validateIconLottieAnimation(const StickerIcon &icon);
|
||||
QSize iconBox() const;
|
||||
|
||||
void refreshIconsGeometry(ValidateIconAnimations animations);
|
||||
void refillLottieData();
|
||||
void updateSelected();
|
||||
void updateSetIcon(uint64 setId);
|
||||
void finishDragging();
|
||||
void paintStickerSettingsIcon(Painter &p) const;
|
||||
void paintSearchIcon(Painter &p) const;
|
||||
@@ -134,6 +150,7 @@ private:
|
||||
static constexpr auto kVisibleIconsCount = 8;
|
||||
|
||||
QList<StickerIcon> _icons;
|
||||
mutable base::flat_map<uint64, LottieIcon> _lottieData;
|
||||
OverState _iconOver = SpecialOver::None;
|
||||
int _iconSel = 0;
|
||||
OverState _iconDown = SpecialOver::None;
|
||||
@@ -191,7 +208,8 @@ StickersListWidget::Set &StickersListWidget::Set::operator=(
|
||||
Set &&other) = default;
|
||||
StickersListWidget::Set::~Set() = default;
|
||||
|
||||
StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent) : InnerFooter(parent)
|
||||
StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent)
|
||||
: InnerFooter(parent)
|
||||
, _pan(parent)
|
||||
, _iconsAnimation([=](crl::time now) {
|
||||
return iconsAnimationCallback(now);
|
||||
@@ -205,6 +223,34 @@ StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent) : Inner
|
||||
});
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::clearLottieData() {
|
||||
for (auto &icon : _icons) {
|
||||
icon.lottie = nullptr;
|
||||
}
|
||||
_lottieData.clear();
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::refillLottieData() {
|
||||
for (auto &item : _lottieData) {
|
||||
item.second.stale = true;
|
||||
}
|
||||
for (auto &icon : _icons) {
|
||||
const auto i = _lottieData.find(icon.setId);
|
||||
if (i == end(_lottieData)) {
|
||||
continue;
|
||||
}
|
||||
icon.lottie = i->second.player.get();
|
||||
i->second.stale = false;
|
||||
}
|
||||
for (auto i = begin(_lottieData); i != end(_lottieData);) {
|
||||
if (i->second.stale) {
|
||||
i = _lottieData.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::initSearch() {
|
||||
_searchField.create(
|
||||
this,
|
||||
@@ -282,7 +328,7 @@ void StickersListWidget::Footer::enumerateVisibleIcons(Callback callback) {
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::preloadImages() {
|
||||
enumerateVisibleIcons([](const StickerIcon & icon, int x) {
|
||||
enumerateVisibleIcons([](const StickerIcon &icon, int x) {
|
||||
if (const auto sticker = icon.sticker) {
|
||||
const auto origin = sticker->stickerSetOrigin();
|
||||
if (icon.thumbnail) {
|
||||
@@ -607,6 +653,7 @@ void StickersListWidget::Footer::updateSelected() {
|
||||
void StickersListWidget::Footer::refreshIcons(
|
||||
ValidateIconAnimations animations) {
|
||||
_pan->fillIcons(_icons);
|
||||
refillLottieData();
|
||||
refreshIconsGeometry(animations);
|
||||
}
|
||||
|
||||
@@ -654,6 +701,49 @@ void StickersListWidget::Footer::paintFeaturedStickerSetsBadge(Painter &p, int i
|
||||
}
|
||||
}
|
||||
|
||||
QSize StickersListWidget::Footer::iconBox() const {
|
||||
return QSize(
|
||||
st::stickerIconWidth - 2 * st::stickerIconPadding,
|
||||
st::emojiFooterHeight - 2 * st::stickerIconPadding);
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::validateIconLottieAnimation(
|
||||
const StickerIcon &icon) {
|
||||
if (icon.lottie
|
||||
|| !Stickers::HasLottieThumbnail(ImagePtr(), icon.sticker)) {
|
||||
return;
|
||||
}
|
||||
auto player = Stickers::LottieThumbnail(
|
||||
ImagePtr(),
|
||||
icon.sticker,
|
||||
Stickers::LottieSize::StickersFooter,
|
||||
iconBox() * cIntRetinaFactor(),
|
||||
_pan->getLottieRenderer());
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
icon.lottie = player.get();
|
||||
const auto id = icon.setId;
|
||||
const auto [i, ok] = _lottieData.emplace(
|
||||
id,
|
||||
LottieIcon{ std::move(player) });
|
||||
Assert(ok);
|
||||
|
||||
icon.lottie->updates(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateSetIcon(id);
|
||||
}, i->second.lifetime);
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::updateSetIcon(uint64 setId) {
|
||||
enumerateVisibleIcons([&](const StickerIcon &icon, int x) {
|
||||
if (icon.setId != setId) {
|
||||
return;
|
||||
}
|
||||
update(x, _iconsTop, st::stickerIconWidth, st::emojiFooterHeight);
|
||||
});
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::paintSetIcon(
|
||||
Painter &p,
|
||||
const StickerIcon &icon,
|
||||
@@ -663,11 +753,35 @@ void StickersListWidget::Footer::paintSetIcon(
|
||||
const auto thumb = icon.thumbnail
|
||||
? icon.thumbnail.get()
|
||||
: icon.sticker->thumbnail();
|
||||
if (thumb) {
|
||||
thumb->load(origin);
|
||||
if (!thumb) {
|
||||
return;
|
||||
}
|
||||
thumb->load(origin);
|
||||
if (!thumb->loaded()) {
|
||||
return;
|
||||
}
|
||||
const_cast<Footer*>(this)->validateIconLottieAnimation(icon);
|
||||
if (!icon.lottie) {
|
||||
auto pix = thumb->pix(origin, icon.pixw, icon.pixh);
|
||||
|
||||
p.drawPixmapLeft(x + (st::stickerIconWidth - icon.pixw) / 2, _iconsTop + (st::emojiFooterHeight - icon.pixh) / 2, width(), pix);
|
||||
} else if (icon.lottie->ready()) {
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = iconBox() * cIntRetinaFactor();
|
||||
const auto frame = icon.lottie->frame(request);
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
p.drawImage(
|
||||
QRect(
|
||||
x + (st::stickerIconWidth - size.width()) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height()),
|
||||
frame);
|
||||
const auto paused = _pan->controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
if (!paused) {
|
||||
icon.lottie->markFrameShown();
|
||||
}
|
||||
}
|
||||
} else if (icon.megagroup) {
|
||||
icon.megagroup->paintUserpicLeft(p, x + (st::stickerIconWidth - st::stickerGroupCategorySize) / 2, _iconsTop + (st::emojiFooterHeight - st::stickerGroupCategorySize) / 2, width(), st::stickerGroupCategorySize);
|
||||
@@ -1901,11 +2015,17 @@ TabbedSelector::InnerFooter *StickersListWidget::getFooter() const {
|
||||
void StickersListWidget::processHideFinished() {
|
||||
clearSelection();
|
||||
clearLottieData();
|
||||
if (_footer) {
|
||||
_footer->clearLottieData();
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::processPanelHideFinished() {
|
||||
clearInstalledLocally();
|
||||
clearLottieData();
|
||||
if (_footer) {
|
||||
_footer->clearLottieData();
|
||||
}
|
||||
// Preserve panel state through visibility toggles.
|
||||
//// Reset to the recent stickers section.
|
||||
//if (_section == Section::Featured && (!_footer || !_footer->hasOnlyFeaturedSets())) {
|
||||
|
Reference in New Issue
Block a user