/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/stickers_list_widget.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_session.h" #include "data/data_channel.h" #include "data/data_file_origin.h" #include "data/data_cloud_file.h" #include "data/data_changes.h" #include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu #include "chat_helpers/stickers_lottie.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/effects/animations.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/path_shift_gradient.h" #include "ui/image/image.h" #include "ui/cached_round_corners.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" #include "storage/storage_account.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "dialogs/dialogs_layout.h" #include "boxes/sticker_set_box.h" #include "boxes/stickers_box.h" #include "boxes/confirm_box.h" #include "window/window_session_controller.h" // GifPauseReason. #include "main/main_session.h" #include "main/main_session_settings.h" #include "apiwrap.h" #include "api/api_toggling_media.h" // Api::ToggleFavedSticker #include "styles/style_chat_helpers.h" #include "styles/style_window.h" #include namespace ChatHelpers { namespace { constexpr auto kSearchRequestDelay = 400; constexpr auto kRecentDisplayLimit = 20; constexpr auto kPreloadOfficialPages = 4; constexpr auto kOfficialLoadLimit = 40; using Data::StickersSet; using Data::StickersPack; using Data::StickersSetThumbnailView; using SetFlag = Data::StickersSetFlag; [[nodiscard]] bool SetInMyList(Data::StickersSetFlags flags) { return (flags & SetFlag::Installed) && !(flags & SetFlag::Archived); } } // namespace struct StickerIcon { StickerIcon(uint64 setId) : setId(setId) { } StickerIcon( not_null set, DocumentData *sticker, int pixw, int pixh) : setId(set->id) , set(set) , sticker(sticker) , pixw(pixw) , pixh(pixh) { } void ensureMediaCreated() const { if (!sticker) { return; } else if (set->hasThumbnail()) { if (!thumbnailMedia) { thumbnailMedia = set->createThumbnailView(); set->loadThumbnail(); } } else if (!stickerMedia) { stickerMedia = sticker->createMediaView(); stickerMedia->thumbnailWanted(sticker->stickerSetOrigin()); } } uint64 setId = 0; StickersSet *set = nullptr; mutable std::unique_ptr lottie; mutable QPixmap savedFrame; DocumentData *sticker = nullptr; ChannelData *megagroup = nullptr; mutable std::shared_ptr thumbnailMedia; mutable std::shared_ptr stickerMedia; mutable std::shared_ptr megagroupUserpic; int pixw = 0; int pixh = 0; mutable rpl::lifetime lifetime; }; class StickersListWidget::Footer : public TabbedSelector::InnerFooter { public: explicit Footer( not_null parent, bool searchButtonVisible); void preloadImages(); void validateSelectedIcon( uint64 setId, ValidateIconAnimations animations); void refreshIcons(ValidateIconAnimations animations); bool hasOnlyFeaturedSets() const; void leaveToChildEvent(QEvent *e, QWidget *child) override; void stealFocus(); void returnFocus(); void setLoading(bool loading); void clearHeavyData(); rpl::producer<> openSettingsRequests() const; protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; bool eventHook(QEvent *e) override; void processHideFinished() override; private: enum class SpecialOver { None, Search, Settings, }; using OverState = std::variant; void enumerateVisibleIcons(Fn callback); bool iconsAnimationCallback(crl::time now); void setSelectedIcon( int newSelected, ValidateIconAnimations animations); void validateIconLottieAnimation(const StickerIcon &icon); void refreshIconsGeometry(ValidateIconAnimations animations); void updateSelected(); void updateSetIcon(uint64 setId); void finishDragging(); void paintStickerSettingsIcon(Painter &p) const; void paintSearchIcon(Painter &p) const; void paintSetIcon(Painter &p, const StickerIcon &icon, int x) const; void paintSelectionBar(Painter &p) const; void paintLeftRightFading(Painter &p) const; void initSearch(); void toggleSearch(bool visible); void resizeSearchControls(); void scrollByWheelEvent(not_null e); const not_null _pan; const bool _searchButtonVisible = true; static constexpr auto kVisibleIconsCount = 8; std::vector _icons; OverState _iconOver = SpecialOver::None; int _iconSel = 0; OverState _iconDown = SpecialOver::None; bool _iconsDragging = false; Ui::Animations::Basic _iconsAnimation; QPoint _iconsMousePos, _iconsMouseDown; int _iconsLeft = 0; int _iconsRight = 0; int _iconsTop = 0; int _iconsStartX = 0; int _iconsMax = 0; anim::value _iconsX; anim::value _iconSelX; crl::time _iconsStartAnim = 0; bool _horizontal = false; bool _searchShown = false; object_ptr _searchField = { nullptr }; object_ptr _searchCancel = { nullptr }; QPointer _focusTakenFrom; rpl::event_stream<> _openSettingsRequests; }; auto StickersListWidget::PrepareStickers( const QVector &pack) -> std::vector { return ranges::views::all( pack ) | ranges::views::transform([](DocumentData *document) { return Sticker{ document }; }) | ranges::to_vector; } StickersListWidget::Set::Set( uint64 id, StickersSet *set, Data::StickersSetFlags flags, const QString &title, const QString &shortName, int count, bool externalLayout, std::vector &&stickers) : id(id) , set(set) , flags(flags) , title(title) , shortName(shortName) , stickers(std::move(stickers)) , count(count) , externalLayout(externalLayout) { } StickersListWidget::Set::Set(Set &&other) = default; StickersListWidget::Set &StickersListWidget::Set::operator=( Set &&other) = default; StickersListWidget::Set::~Set() = default; void StickersListWidget::Sticker::ensureMediaCreated() { if (documentMedia) { return; } documentMedia = document->createMediaView(); } StickersListWidget::Footer::Footer( not_null parent, bool searchButtonVisible) : InnerFooter(parent) , _pan(parent) , _searchButtonVisible(searchButtonVisible) , _iconsAnimation([=](crl::time now) { return iconsAnimationCallback(now); }) { setMouseTracking(true); _iconsLeft = st::emojiCategorySkip + (_searchButtonVisible ? st::stickerIconWidth : 0); _iconsRight = st::emojiCategorySkip + st::stickerIconWidth; _pan->session().downloaderTaskFinished( ) | rpl::start_with_next([=] { update(); }, lifetime()); } void StickersListWidget::Footer::clearHeavyData() { const auto count = int(_icons.size()); const auto iconsX = qRound(_iconsX.current()); const auto first = floorclamp(iconsX, st::stickerIconWidth, 0, count); const auto last = ceilclamp( iconsX + width(), st::stickerIconWidth, 0, count); for (auto i = 0; i != count; ++i) { auto &icon = _icons[i]; icon.lottie = nullptr; icon.lifetime.destroy(); icon.stickerMedia = nullptr; if (i < first || i >= last) { // Not visible icon. icon.savedFrame = QPixmap(); } } } void StickersListWidget::Footer::initSearch() { _searchField.create( this, st::gifsSearchField, tr::lng_stickers_search_sets()); _searchCancel.create(this, st::gifsSearchCancel); _searchField->show(); _searchCancel->show(anim::type::instant); const auto cancelSearch = [=] { if (_searchField->getLastText().isEmpty()) { toggleSearch(false); } else { _searchField->setText(QString()); } }; connect(_searchField, &Ui::InputField::submitted, [=] { _pan->sendSearchRequest(); }); connect(_searchField, &Ui::InputField::cancelled, cancelSearch); connect(_searchField, &Ui::InputField::changed, [=] { _pan->searchForSets(_searchField->getLastText()); }); _searchCancel->setClickedCallback(cancelSearch); resizeSearchControls(); } void StickersListWidget::Footer::toggleSearch(bool visible) { if (_searchShown == visible) { return; } _searchShown = visible; if (_searchShown) { initSearch(); stealFocus(); } else if (_searchField) { returnFocus(); _searchField.destroy(); _searchCancel.destroy(); _focusTakenFrom = nullptr; } update(); } void StickersListWidget::Footer::stealFocus() { if (_searchField) { if (!_focusTakenFrom) { _focusTakenFrom = QApplication::focusWidget(); } _searchField->setFocus(); } } void StickersListWidget::Footer::returnFocus() { if (_searchField && _focusTakenFrom) { if (_searchField->hasFocus()) { _focusTakenFrom->setFocus(); } _focusTakenFrom = nullptr; } } void StickersListWidget::Footer::enumerateVisibleIcons( Fn callback) { auto iconsX = qRound(_iconsX.current()); auto x = _iconsLeft - (iconsX % st::stickerIconWidth); auto first = floorclamp(iconsX, st::stickerIconWidth, 0, _icons.size()); auto last = ceilclamp( iconsX + width(), st::stickerIconWidth, 0, _icons.size()); for (auto index = first; index != last; ++index) { callback(_icons[index], x); x += st::stickerIconWidth; } } void StickersListWidget::Footer::preloadImages() { enumerateVisibleIcons([](const StickerIcon &icon, int x) { if (const auto sticker = icon.sticker) { Assert(icon.set != nullptr); if (icon.set->hasThumbnail()) { icon.set->loadThumbnail(); } else { sticker->loadThumbnail(sticker->stickerSetOrigin()); } } }); } void StickersListWidget::Footer::validateSelectedIcon( uint64 setId, ValidateIconAnimations animations) { auto favedIconIndex = -1; auto newSelected = -1; for (auto i = 0, l = int(_icons.size()); i != l; ++i) { if (_icons[i].setId == setId || (_icons[i].setId == Data::Stickers::FavedSetId && setId == Data::Stickers::RecentSetId)) { newSelected = i; break; } else if (_icons[i].setId == Data::Stickers::FavedSetId) { favedIconIndex = i; } } setSelectedIcon( (newSelected >= 0 ? newSelected : (favedIconIndex >= 0) ? favedIconIndex : 0), animations); } void StickersListWidget::Footer::setSelectedIcon( int newSelected, ValidateIconAnimations animations) { if (_iconSel == newSelected) { return; } _iconSel = newSelected; auto iconSelXFinal = _iconSel * st::stickerIconWidth; if (animations == ValidateIconAnimations::Full) { _iconSelX.start(iconSelXFinal); } else { _iconSelX = anim::value(iconSelXFinal, iconSelXFinal); } auto iconsCountForCentering = (2 * _iconSel + 1); auto iconsWidthForCentering = iconsCountForCentering * st::stickerIconWidth; auto iconsXFinal = std::clamp( (_iconsLeft + iconsWidthForCentering + _iconsRight - width()) / 2, 0, _iconsMax); if (animations == ValidateIconAnimations::None) { _iconsX = anim::value(iconsXFinal, iconsXFinal); _iconsAnimation.stop(); } else { _iconsX.start(iconsXFinal); _iconsStartAnim = crl::now(); _iconsAnimation.start(); } updateSelected(); update(); } void StickersListWidget::Footer::processHideFinished() { _iconOver = _iconDown = SpecialOver::None; _iconsStartAnim = 0; _iconsAnimation.stop(); _iconsX.finish(); _iconSelX.finish(); _horizontal = false; } void StickersListWidget::Footer::leaveToChildEvent(QEvent *e, QWidget *child) { _iconsMousePos = QCursor::pos(); updateSelected(); } void StickersListWidget::Footer::setLoading(bool loading) { if (_searchCancel) { _searchCancel->setLoadingAnimation(loading); } } void StickersListWidget::Footer::paintEvent(QPaintEvent *e) { Painter p(this); paintSearchIcon(p); if (_icons.empty() || _searchShown) { return; } if (!hasOnlyFeaturedSets()) { paintStickerSettingsIcon(p); } auto clip = QRect( _iconsLeft, _iconsTop, width() - _iconsLeft - _iconsRight, st::emojiFooterHeight); if (rtl()) { clip.moveLeft(width() - _iconsLeft - clip.width()); } p.setClipRect(clip); enumerateVisibleIcons([&](const StickerIcon &icon, int x) { paintSetIcon(p, icon, x); }); paintSelectionBar(p); paintLeftRightFading(p); } void StickersListWidget::Footer::paintSelectionBar(Painter &p) const { auto selxrel = _iconsLeft + qRound(_iconSelX.current()); auto selx = selxrel - qRound(_iconsX.current()); if (rtl()) { selx = width() - selx - st::stickerIconWidth; } p.fillRect( selx, _iconsTop + st::emojiFooterHeight - st::stickerIconPadding, st::stickerIconWidth, st::stickerIconSel, st::stickerIconSelColor); } void StickersListWidget::Footer::paintLeftRightFading(Painter &p) const { auto o_left = std::clamp( _iconsX.current() / st::stickerIconLeft.width(), 0., 1.); if (o_left > 0) { p.setOpacity(o_left); st::stickerIconLeft.fill(p, style::rtlrect(_iconsLeft, _iconsTop, st::stickerIconLeft.width(), st::emojiFooterHeight, width())); p.setOpacity(1.); } auto o_right = std::clamp( (_iconsMax - _iconsX.current()) / st::stickerIconRight.width(), 0., 1.); if (o_right > 0) { p.setOpacity(o_right); st::stickerIconRight.fill(p, style::rtlrect(width() - _iconsRight - st::stickerIconRight.width(), _iconsTop, st::stickerIconRight.width(), st::emojiFooterHeight, width())); p.setOpacity(1.); } } void StickersListWidget::Footer::resizeEvent(QResizeEvent *e) { if (_searchField) { resizeSearchControls(); } refreshIconsGeometry(ValidateIconAnimations::None); } void StickersListWidget::Footer::resizeSearchControls() { Expects(_searchField != nullptr); Expects(_searchCancel != nullptr); const auto fieldWidth = width() - st::gifsSearchFieldPosition.x() - st::gifsSearchCancelPosition.x() - st::gifsSearchCancel.width; _searchField->resizeToWidth(fieldWidth); _searchField->moveToLeft(st::gifsSearchFieldPosition.x(), st::gifsSearchFieldPosition.y()); _searchCancel->moveToRight(st::gifsSearchCancelPosition.x(), st::gifsSearchCancelPosition.y()); } rpl::producer<> StickersListWidget::Footer::openSettingsRequests() const { return _openSettingsRequests.events(); } void StickersListWidget::Footer::mousePressEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { return; } _iconsMousePos = e ? e->globalPos() : QCursor::pos(); updateSelected(); if (_iconOver == SpecialOver::Settings) { _openSettingsRequests.fire({}); } else if (_iconOver == SpecialOver::Search) { toggleSearch(true); } else { _iconDown = _iconOver; _iconsMouseDown = _iconsMousePos; _iconsStartX = qRound(_iconsX.current()); } } void StickersListWidget::Footer::mouseMoveEvent(QMouseEvent *e) { _iconsMousePos = e ? e->globalPos() : QCursor::pos(); updateSelected(); if (!_iconsDragging && !_icons.empty() && v::is(_iconDown)) { if ((_iconsMousePos - _iconsMouseDown).manhattanLength() >= QApplication::startDragDistance()) { _iconsDragging = true; } } if (_iconsDragging) { auto newX = std::clamp( (rtl() ? -1 : 1) * (_iconsMouseDown.x() - _iconsMousePos.x()) + _iconsStartX, 0, _iconsMax); if (newX != qRound(_iconsX.current())) { _iconsX = anim::value(newX, newX); _iconsStartAnim = 0; _iconsAnimation.stop(); update(); } } } void StickersListWidget::Footer::mouseReleaseEvent(QMouseEvent *e) { if (_icons.empty()) { return; } const auto wasDown = std::exchange(_iconDown, SpecialOver::None); _iconsMousePos = e ? e->globalPos() : QCursor::pos(); if (_iconsDragging) { finishDragging(); return; } updateSelected(); if (wasDown == _iconOver) { if (const auto index = std::get_if(&_iconOver)) { _iconSelX = anim::value( *index * st::stickerIconWidth, *index * st::stickerIconWidth); _pan->showStickerSet(_icons[*index].setId); } } } void StickersListWidget::Footer::finishDragging() { auto newX = std::clamp( _iconsStartX + _iconsMouseDown.x() - _iconsMousePos.x(), 0, _iconsMax); if (newX != qRound(_iconsX.current())) { _iconsX = anim::value(newX, newX); _iconsStartAnim = 0; _iconsAnimation.stop(); update(); } _iconsDragging = false; updateSelected(); } bool StickersListWidget::Footer::eventHook(QEvent *e) { if (e->type() == QEvent::TouchBegin) { } else if (e->type() == QEvent::Wheel) { if (!_icons.empty() && v::is(_iconOver) && (_iconDown == SpecialOver::None)) { scrollByWheelEvent(static_cast(e)); } } return InnerFooter::eventHook(e); } void StickersListWidget::Footer::scrollByWheelEvent( not_null e) { auto horizontal = (e->angleDelta().x() != 0); auto vertical = (e->angleDelta().y() != 0); if (horizontal) { _horizontal = true; } auto newX = qRound(_iconsX.current()); if (/*_horizontal && */horizontal) { newX = std::clamp( newX - (rtl() ? -1 : 1) * (e->pixelDelta().x() ? e->pixelDelta().x() : e->angleDelta().x()), 0, _iconsMax); } else if (/*!_horizontal && */vertical) { newX = std::clamp( newX - (e->pixelDelta().y() ? e->pixelDelta().y() : e->angleDelta().y()), 0, _iconsMax); } if (newX != qRound(_iconsX.current())) { _iconsX = anim::value(newX, newX); _iconsStartAnim = 0; _iconsAnimation.stop(); updateSelected(); update(); } } void StickersListWidget::Footer::updateSelected() { if (_iconDown != SpecialOver::None) { return; } auto p = mapFromGlobal(_iconsMousePos); auto x = p.x(), y = p.y(); if (rtl()) x = width() - x; const auto settingsLeft = width() - _iconsRight; const auto searchLeft = _iconsLeft - st::stickerIconWidth; auto newOver = OverState(SpecialOver::None); if (_searchButtonVisible && x >= searchLeft && x < searchLeft + st::stickerIconWidth && y >= _iconsTop && y < _iconsTop + st::emojiFooterHeight) { newOver = SpecialOver::Search; } else if (x >= settingsLeft && x < settingsLeft + st::stickerIconWidth && y >= _iconsTop && y < _iconsTop + st::emojiFooterHeight) { if (!_icons.empty() && !hasOnlyFeaturedSets()) { newOver = SpecialOver::Settings; } } else if (!_icons.empty()) { if (y >= _iconsTop && y < _iconsTop + st::emojiFooterHeight && x >= _iconsLeft && x < width() - _iconsRight) { x += qRound(_iconsX.current()) - _iconsLeft; if (x < _icons.size() * st::stickerIconWidth) { newOver = qFloor(x / st::stickerIconWidth); } } } if (newOver != _iconOver) { if (newOver == SpecialOver::None) { setCursor(style::cur_default); } else if (_iconOver == SpecialOver::None) { setCursor(style::cur_pointer); } _iconOver = newOver; } } void StickersListWidget::Footer::refreshIcons( ValidateIconAnimations animations) { auto icons = _pan->fillIcons(); auto indices = base::flat_map(); indices.reserve(_icons.size()); auto index = 0; for (const auto &entry : _icons) { indices.emplace(entry.setId, index++); } for (auto &now : icons) { if (const auto i = indices.find(now.setId); i != end(indices)) { auto &was = _icons[i->second]; if (now.sticker == was.sticker) { now.lottie = std::move(was.lottie); now.lifetime = std::move(was.lifetime); now.savedFrame = std::move(was.savedFrame); } } } _icons = std::move(icons); refreshIconsGeometry(animations); } void StickersListWidget::Footer::refreshIconsGeometry( ValidateIconAnimations animations) { _iconOver = _iconDown = SpecialOver::None; _iconsX.finish(); _iconSelX.finish(); _iconsStartAnim = 0; _iconsAnimation.stop(); _iconsMax = std::max( _iconsLeft + int(_icons.size()) * st::stickerIconWidth + _iconsRight - width(), 0); if (_iconsX.current() > _iconsMax) { _iconsX = anim::value(_iconsMax, _iconsMax); } updateSelected(); _pan->validateSelectedIcon(animations); update(); } bool StickersListWidget::Footer::hasOnlyFeaturedSets() const { return (_icons.size() == 1) && (_icons[0].setId == Data::Stickers::FeaturedSetId); } void StickersListWidget::Footer::paintStickerSettingsIcon(Painter &p) const { const auto settingsLeft = width() - _iconsRight; st::stickersSettings.paint( p, settingsLeft + (st::stickerIconWidth - st::stickersSettings.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width()); } void StickersListWidget::Footer::paintSearchIcon(Painter &p) const { const auto searchLeft = _iconsLeft - st::stickerIconWidth; st::stickersSearch.paint( p, searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width()); } void StickersListWidget::Footer::validateIconLottieAnimation( const StickerIcon &icon) { icon.ensureMediaCreated(); if (icon.lottie || !icon.sticker || !HasLottieThumbnail( icon.thumbnailMedia.get(), icon.stickerMedia.get())) { return; } auto player = LottieThumbnail( icon.thumbnailMedia.get(), icon.stickerMedia.get(), StickerLottieSize::StickersFooter, QSize( st::stickerIconWidth - 2 * st::stickerIconPadding, st::emojiFooterHeight - 2 * st::stickerIconPadding ) * cIntRetinaFactor(), _pan->getLottieRenderer()); if (!player) { return; } icon.lottie = std::move(player); const auto id = icon.setId; icon.lottie->updates( ) | rpl::start_with_next([=] { updateSetIcon(id); }, icon.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, int x) const { if (icon.sticker) { icon.ensureMediaCreated(); const_cast(this)->validateIconLottieAnimation(icon); const auto origin = icon.sticker->stickerSetOrigin(); const auto thumb = icon.thumbnailMedia ? icon.thumbnailMedia->image() : icon.stickerMedia ? icon.stickerMedia->thumbnail() : nullptr; if (!icon.lottie || (!icon.lottie->ready() && !icon.savedFrame.isNull())) { const auto pixmap = !icon.savedFrame.isNull() ? icon.savedFrame : (!icon.lottie && thumb) ? thumb->pix(icon.pixw, icon.pixh) : QPixmap(); if (pixmap.isNull()) { return; } else if (icon.savedFrame.isNull()) { icon.savedFrame = pixmap; } p.drawPixmapLeft( x + (st::stickerIconWidth - icon.pixw) / 2, _iconsTop + (st::emojiFooterHeight - icon.pixh) / 2, width(), pixmap); } else if (icon.lottie->ready()) { const auto frame = icon.lottie->frame(); const auto size = frame.size() / cIntRetinaFactor(); if (icon.savedFrame.isNull()) { icon.savedFrame = QPixmap::fromImage(frame, Qt::ColorOnly); icon.savedFrame.setDevicePixelRatio(cRetinaFactor()); } 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, icon.megagroupUserpic, x + (st::stickerIconWidth - st::stickerGroupCategorySize) / 2, _iconsTop + (st::emojiFooterHeight - st::stickerGroupCategorySize) / 2, width(), st::stickerGroupCategorySize); } else { const auto paintedIcon = [&] { if (icon.setId == Data::Stickers::FeaturedSetId) { const auto session = &_pan->session(); return session->data().stickers().featuredSetsUnreadCount() ? &st::stickersTrendingUnread : &st::stickersTrending; //} else if (setId == Stickers::FavedSetId) { // return &st::stickersFaved; } return &st::stickersRecent; }(); paintedIcon->paint( p, x + (st::stickerIconWidth - paintedIcon->width()) / 2, _iconsTop + (st::emojiFooterHeight - paintedIcon->height()) / 2, width()); } } bool StickersListWidget::Footer::iconsAnimationCallback(crl::time now) { if (anim::Disabled()) { now += st::stickerIconMove; } if (_iconsStartAnim) { const auto dt = (now - _iconsStartAnim) / float64(st::stickerIconMove); if (dt >= 1.) { _iconsStartAnim = 0; _iconsX.finish(); _iconSelX.finish(); } else { _iconsX.update(dt, anim::linear); _iconSelX.update(dt, anim::linear); } } update(); return (_iconsStartAnim != 0); } StickersListWidget::StickersListWidget( QWidget *parent, not_null controller, bool masks) : Inner(parent, controller) , _api(&controller->session().mtp()) , _section(Section::Stickers) , _isMasks(masks) , _pathGradient(std::make_unique( st::windowBgRipple, st::windowBgOver, [=] { update(); })) , _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st::emojiPanHeaderLeft) , _addText(tr::lng_stickers_featured_add(tr::now).toUpper()) , _addWidth(st::stickersTrendingAdd.font->width(_addText)) , _settings(this, tr::lng_stickers_you_have(tr::now)) , _previewTimer([=] { showPreview(); }) , _searchRequestTimer([=] { sendSearchRequest(); }) { setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); _settings->addClickHandler([=] { using Section = StickersBox::Section; controller->show( Box(controller, Section::Installed, _isMasks), Ui::LayerOption::KeepOther); }); session().downloaderTaskFinished( ) | rpl::start_with_next([=] { if (isVisible()) { update(); readVisibleFeatured(getVisibleTop(), getVisibleBottom()); } }, lifetime()); session().changes().peerUpdates( Data::PeerUpdate::Flag::StickersSet ) | rpl::filter([=](const Data::PeerUpdate &update) { return (update.peer.get() == _megagroupSet); }) | rpl::start_with_next([=] { refreshStickers(); }, lifetime()); session().data().stickers().recentUpdated( ) | rpl::start_with_next([=](Data::Stickers::Recent recent) { const auto attached = (recent == Data::Stickers::Recent::Attached); if (attached != _isMasks) { return; } refreshRecent(); }, lifetime()); } Main::Session &StickersListWidget::session() const { return controller()->session(); } rpl::producer StickersListWidget::chosen() const { return _chosen.events(); } rpl::producer<> StickersListWidget::scrollUpdated() const { return _scrollUpdated.events(); } rpl::producer<> StickersListWidget::checkForHide() const { return _checkForHide.events(); } object_ptr StickersListWidget::createFooter() { Expects(_footer == nullptr); auto result = object_ptr