From 76630528f7376749032ebaa02de0221205c9109d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 2 Jul 2019 15:20:04 +0200 Subject: [PATCH] Show animated thumbnails in sets box. --- .../SourceFiles/boxes/sticker_set_box.cpp | 4 +- Telegram/SourceFiles/boxes/stickers_box.cpp | 181 +++++++++++++----- Telegram/SourceFiles/boxes/stickers_box.h | 13 +- .../chat_helpers/stickers_list_widget.cpp | 18 +- .../SourceFiles/lottie/lottie_animation.cpp | 6 + .../SourceFiles/lottie/lottie_animation.h | 1 + .../lottie/lottie_single_player.cpp | 4 + .../SourceFiles/lottie/lottie_single_player.h | 1 + 8 files changed, 168 insertions(+), 60 deletions(-) diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index a07ce4a8f..3be3968d4 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -559,14 +559,12 @@ void StickerSetBox::Inner::paintSticker( if (h < 1) h = 1; QPoint ppos = position + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2); if (element.animated && element.animated->ready()) { - auto request = Lottie::FrameRequest(); - request.box = boundingBoxSize() * cIntRetinaFactor(); const auto paused = _controller->isGifPausedAtLeastFor( Window::GifPauseReason::Layer); if (!paused) { element.animated->markFrameShown(); } - const auto frame = element.animated->frame(request); + const auto frame = element.animated->frame(); p.drawImage( QRect(ppos, frame.size() / cIntRetinaFactor()), frame); diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index b0b87920e..715072d53 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -20,8 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "storage/localstorage.h" #include "dialogs/dialogs_layout.h" -#include "styles/style_boxes.h" -#include "styles/style_chat_helpers.h" +#include "lottie/lottie_single_player.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" @@ -31,7 +30,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/input_fields.h" #include "ui/image/image.h" +#include "window/window_session_controller.h" #include "auth_session.h" +#include "styles/style_boxes.h" +#include "styles/style_chat_helpers.h" namespace { @@ -655,7 +657,8 @@ StickersBox::Inner::Row::Row( StickersBox::Inner::Row::~Row() = default; -StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : TWidget(parent) +StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) +: RpWidget(parent) , _section(section) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _shiftingAnimation([=](crl::time now) { @@ -669,7 +672,8 @@ StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : TWidg setup(); } -StickersBox::Inner::Inner(QWidget *parent, not_null megagroup) : TWidget(parent) +StickersBox::Inner::Inner(QWidget *parent, not_null megagroup) +: RpWidget(parent) , _section(StickersBox::Section::Installed) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _shiftingAnimation([=](crl::time now) { @@ -794,7 +798,7 @@ QRect StickersBox::Inner::relativeButtonRect(bool removeButton) const { return QRect(buttonx, buttony, buttonw, buttonh); } -void StickersBox::Inner::paintRow(Painter &p, Row *set, int index) { +void StickersBox::Inner::paintRow(Painter &p, not_null set, int index) { auto xadd = 0, yadd = qRound(set->yadd.current()); if (xadd || yadd) p.translate(xadd, yadd); @@ -854,17 +858,7 @@ void StickersBox::Inner::paintRow(Painter &p, Row *set, int index) { } if (set->sticker) { - const auto origin = Data::FileOriginStickerSet( - set->id, - set->accessHash); - const auto thumb = set->thumbnail - ? set->thumbnail.get() - : set->sticker->thumbnail(); - if (thumb) { - thumb->load(origin); - auto pix = thumb->pix(origin, set->pixw, set->pixh); - p.drawPixmapLeft(stickerx + (st::contactsPhotoSize - set->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - set->pixh) / 2, width(), pix); - } + paintRowThumbnail(p, set, stickerx); } int namex = stickerx + st::contactsPhotoSize + st::contactsPadding.left(); @@ -897,7 +891,97 @@ void StickersBox::Inner::paintRow(Painter &p, Row *set, int index) { if (xadd || yadd) p.translate(-xadd, -yadd); } -void StickersBox::Inner::paintFakeButton(Painter &p, Row *set, int index) { +void StickersBox::Inner::paintRowThumbnail( + Painter &p, + not_null set, + int left) { + const auto origin = Data::FileOriginStickerSet( + set->id, + set->accessHash); + const auto thumb = set->thumbnail + ? set->thumbnail.get() + : set->sticker->thumbnail(); + if (!thumb) { + return; + } + thumb->load(origin); + validateLottieAnimation(set); + if (!set->lottie) { + if (!thumb->loaded()) { + return; + } + p.drawPixmapLeft( + left + (st::contactsPhotoSize - set->pixw) / 2, + st::contactsPadding.top() + (st::contactsPhotoSize - set->pixh) / 2, + width(), + thumb->pix(origin, set->pixw, set->pixh)); + } else if (set->lottie->ready()) { + const auto frame = set->lottie->frame(); + const auto size = frame.size() / cIntRetinaFactor(); + p.drawImage( + QRect( + left + (st::contactsPhotoSize - size.width()) / 2, + st::contactsPadding.top() + (st::contactsPhotoSize - size.height()) / 2, + size.width(), + size.height()), + frame); + const auto controller = App::wnd()->sessionController(); + const auto paused = controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); + if (!paused) { + set->lottie->markFrameShown(); + } + } +} + +void StickersBox::Inner::validateLottieAnimation(not_null set) { + if (set->lottie + || !Stickers::HasLottieThumbnail(set->thumbnail, set->sticker)) { + return; + } + auto player = Stickers::LottieThumbnail( + set->thumbnail, + set->sticker, + Stickers::LottieSize::SetsListThumbnail, + QSize( + st::contactsPhotoSize, + st::contactsPhotoSize) * cIntRetinaFactor()); + if (!player) { + return; + } + set->lottie = std::move(player); + set->lottie->updates( + ) | rpl::start_with_next([=] { + updateRowThumbnail(set); + }, lifetime()); +} + +void StickersBox::Inner::updateRowThumbnail(not_null set) { + const auto rowTop = [&] { + if (set == _megagroupSelectedSet.get()) { + return _megagroupDivider->y() - _rowHeight; + } + auto top = _itemsTop; + for (const auto &row : _rows) { + if (row.get() == set) { + return top + qRound(row->yadd.current()); + } + top += _rowHeight; + } + Unexpected("StickersBox::Inner::updateRowThumbnail: row not found"); + }(); + const auto left = st::contactsPadding.left() + + ((!_megagroupSet && _section == Section::Installed) + ? st::stickersReorderIcon.width() + st::stickersReorderSkip + : 0); + update( + left, + rowTop + st::contactsPadding.top(), + st::contactsPhotoSize, + st::contactsPhotoSize); +} + +void StickersBox::Inner::paintFakeButton(Painter &p, not_null set, int index) { auto removeButton = (_section == Section::Installed && !set->removed); auto rect = relativeButtonRect(removeButton); if (_section != Section::Installed && set->installed && !set->archived && !set->removed) { @@ -1535,36 +1619,41 @@ void StickersBox::Inner::updateSize(int newWidth) { void StickersBox::Inner::updateRows() { int maxNameWidth = countMaxNameWidth(); auto &sets = Auth().data().stickerSets(); - for_const (auto &row, _rows) { - auto it = sets.constFind(row->id); - if (it != sets.cend()) { - auto &set = it.value(); - if (!row->sticker) { - auto thumbnail = ImagePtr(); - auto sticker = (DocumentData*)nullptr; - auto pixw = 0, pixh = 0; - fillSetCover(set, &thumbnail, &sticker, &pixw, &pixh); - if (sticker) { - row->thumbnail = thumbnail; - row->sticker = sticker; - row->pixw = pixw; - row->pixh = pixh; - } - } - if (!row->isRecentSet()) { - auto wasInstalled = row->installed; - auto wasArchived = row->archived; - fillSetFlags(set, &row->installed, &row->official, &row->unread, &row->archived); - if (_section == Section::Installed) { - row->archived = false; - } - if (row->installed != wasInstalled || row->archived != wasArchived) { - row->ripple.reset(); - } - } - row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth); - row->count = fillSetCount(set); + for (const auto &row : _rows) { + const auto it = sets.constFind(row->id); + if (it == sets.cend()) { + continue; } + const auto &set = it.value(); + if (!row->sticker) { + auto thumbnail = ImagePtr(); + auto sticker = (DocumentData*)nullptr; + auto pixw = 0, pixh = 0; + fillSetCover(set, &thumbnail, &sticker, &pixw, &pixh); + if (sticker) { + if ((row->thumbnail.get() != thumbnail.get()) + || (!thumbnail && row->sticker != sticker)) { + row->lottie = nullptr; + } + row->thumbnail = thumbnail; + row->sticker = sticker; + row->pixw = pixw; + row->pixh = pixh; + } + } + if (!row->isRecentSet()) { + auto wasInstalled = row->installed; + auto wasArchived = row->archived; + fillSetFlags(set, &row->installed, &row->official, &row->unread, &row->archived); + if (_section == Section::Installed) { + row->archived = false; + } + if (row->installed != wasInstalled || row->archived != wasArchived) { + row->ripple.reset(); + } + } + row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth); + row->count = fillSetCount(set); } update(); } diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index dd049d614..1c68eb010 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -136,7 +136,10 @@ private: int stickerPacksCount(bool includeArchivedOfficial = false); // This class is hold in header because it requires Qt preprocessing. -class StickersBox::Inner : public TWidget, private base::Subscriber, private MTP::Sender { +class StickersBox::Inner + : public Ui::RpWidget + , private base::Subscriber + , private MTP::Sender { Q_OBJECT public: @@ -233,6 +236,7 @@ private: int32 pixh = 0; anim::value yadd; std::unique_ptr ripple; + std::unique_ptr lottie; }; struct MegagroupSet { inline bool operator==(const MegagroupSet &other) const { @@ -272,11 +276,14 @@ private: void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton); bool shiftingAnimationCallback(crl::time now); - void paintRow(Painter &p, Row *set, int index); - void paintFakeButton(Painter &p, Row *set, int index); + void paintRow(Painter &p, not_null set, int index); + void paintRowThumbnail(Painter &p, not_null set, int left); + void paintFakeButton(Painter &p, not_null set, int index); void clear(); void setActionSel(int32 actionSel); float64 aboveShadowOpacity() const; + void validateLottieAnimation(not_null set); + void updateRowThumbnail(not_null set); void readVisibleSets(); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index cfac87a28..75e95b57a 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -710,11 +710,11 @@ QSize StickersListWidget::Footer::iconBox() const { void StickersListWidget::Footer::validateIconLottieAnimation( const StickerIcon &icon) { if (icon.lottie - || !Stickers::HasLottieThumbnail(ImagePtr(), icon.sticker)) { + || !Stickers::HasLottieThumbnail(icon.thumbnail, icon.sticker)) { return; } auto player = Stickers::LottieThumbnail( - ImagePtr(), + icon.thumbnail, icon.sticker, Stickers::LottieSize::StickersFooter, iconBox() * cIntRetinaFactor(), @@ -757,14 +757,16 @@ void StickersListWidget::Footer::paintSetIcon( return; } thumb->load(origin); - if (!thumb->loaded()) { - return; - } const_cast(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); + if (!thumb->loaded()) { + return; + } + p.drawPixmapLeft( + x + (st::stickerIconWidth - icon.pixw) / 2, + _iconsTop + (st::emojiFooterHeight - icon.pixh) / 2, + width(), + thumb->pix(origin, icon.pixw, icon.pixh)); } else if (icon.lottie->ready()) { auto request = Lottie::FrameRequest(); request.box = iconBox() * cIntRetinaFactor(); diff --git a/Telegram/SourceFiles/lottie/lottie_animation.cpp b/Telegram/SourceFiles/lottie/lottie_animation.cpp index 657765ea0..ad5c1bda3 100644 --- a/Telegram/SourceFiles/lottie/lottie_animation.cpp +++ b/Telegram/SourceFiles/lottie/lottie_animation.cpp @@ -192,6 +192,12 @@ void Animation::parseFailed(Error error) { _player->failed(this, error); } +QImage Animation::frame() const { + Expects(_state != nullptr); + + return PrepareFrameByRequest(_state->frameForPaint(), true); +} + QImage Animation::frame(const FrameRequest &request) const { Expects(_state != nullptr); diff --git a/Telegram/SourceFiles/lottie/lottie_animation.h b/Telegram/SourceFiles/lottie/lottie_animation.h index 0012d5acc..178b98a7c 100644 --- a/Telegram/SourceFiles/lottie/lottie_animation.h +++ b/Telegram/SourceFiles/lottie/lottie_animation.h @@ -48,6 +48,7 @@ public: const FrameRequest &request); [[nodiscard]] bool ready() const; + [[nodiscard]] QImage frame() const; [[nodiscard]] QImage frame(const FrameRequest &request) const; private: diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.cpp b/Telegram/SourceFiles/lottie/lottie_single_player.cpp index e000881d5..956c4c62d 100644 --- a/Telegram/SourceFiles/lottie/lottie_single_player.cpp +++ b/Telegram/SourceFiles/lottie/lottie_single_player.cpp @@ -68,6 +68,10 @@ bool SinglePlayer::ready() const { return _animation.ready(); } +QImage SinglePlayer::frame() const { + return _animation.frame(); +} + QImage SinglePlayer::frame(const FrameRequest &request) const { return _animation.frame(request); } diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.h b/Telegram/SourceFiles/lottie/lottie_single_player.h index 4d9f86f59..f5c66e713 100644 --- a/Telegram/SourceFiles/lottie/lottie_single_player.h +++ b/Telegram/SourceFiles/lottie/lottie_single_player.h @@ -53,6 +53,7 @@ public: rpl::producer updates() const; [[nodiscard]] bool ready() const; + [[nodiscard]] QImage frame() const; [[nodiscard]] QImage frame(const FrameRequest &request) const; private: