From 90dfae52f59f7ea33d27bb1fcae063fa60f0db69 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Jun 2024 14:48:47 +0400 Subject: [PATCH] Show nice thumbnails for paid albums. --- Telegram/SourceFiles/api/api_credits.cpp | 14 +- .../SourceFiles/boxes/send_credits_box.cpp | 17 +- Telegram/SourceFiles/data/data_credits.h | 20 ++- .../settings/settings_credits_graphics.cpp | 22 +-- .../settings/settings_credits_graphics.h | 9 +- .../ui/effects/credits_graphics.cpp | 166 +++++++++++++----- .../SourceFiles/ui/effects/credits_graphics.h | 27 ++- 7 files changed, 187 insertions(+), 88 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 18c9ee5d9..30f47db63 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -34,7 +34,7 @@ constexpr auto kTransactionsLimit = 100; const auto photo = tl.data().vphoto() ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation()) : nullptr; - auto extended = std::vector(); + auto extended = std::vector(); if (const auto list = tl.data().vextended_media()) { extended.reserve(list->v.size()); for (const auto &media : list->v) { @@ -42,9 +42,9 @@ constexpr auto kTransactionsLimit = 100; if (const auto inner = photo.vphoto()) { const auto photo = owner->processPhoto(*inner); if (!photo->isNull()) { - extended.push_back(CreditsHistoryEntry::Media{ - .type = CreditsHistoryEntry::MediaType::Photo, - .mediaId = photo->id, + extended.push_back(CreditsHistoryMedia{ + .type = CreditsHistoryMediaType::Photo, + .id = photo->id, }); } } @@ -54,9 +54,9 @@ constexpr auto kTransactionsLimit = 100; if (document->isAnimation() || document->isVideoFile() || document->isGifv()) { - extended.push_back(CreditsHistoryEntry::Media{ - .type = CreditsHistoryEntry::MediaType::Video, - .mediaId = document->id, + extended.push_back(CreditsHistoryMedia{ + .type = CreditsHistoryMediaType::Video, + .id = document->id, }); } } diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 442abf25a..cd2cefacd 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -156,11 +156,18 @@ struct PaidMediaData { not_null form, int photoSize) { if (const auto data = LookupPaidMediaData(session, form)) { - const auto first = data.invoice->extendedMedia.front().get(); - if (const auto photo = first->photo()) { - if (photo->extendedMediaPreview()) { - return Settings::PaidMediaPhoto(parent, photo, photoSize); - } + const auto first = data.invoice->extendedMedia[0]->photo(); + const auto second = (data.photos > 1) + ? data.invoice->extendedMedia[1]->photo() + : nullptr; + const auto totalCount = int(data.invoice->extendedMedia.size()); + if (first && first->extendedMediaPreview()) { + return Settings::PaidMediaThumbnail( + parent, + first, + second, + totalCount, + photoSize); } } if (form->photo) { diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index e8d3aec66..75da4db5b 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -19,6 +19,16 @@ struct CreditTopupOption final { using CreditTopupOptions = std::vector; +enum class CreditsHistoryMediaType { + Photo, + Video, +}; + +struct CreditsHistoryMedia { + CreditsHistoryMediaType type = CreditsHistoryMediaType::Photo; + uint64 id = 0; +}; + struct CreditsHistoryEntry final { using PhotoId = uint64; enum class PeerType { @@ -30,21 +40,13 @@ struct CreditsHistoryEntry final { PremiumBot, Ads, }; - enum class MediaType { - Photo, - Video, - }; - struct Media { - MediaType type = MediaType::Photo; - uint64 mediaId = 0; - }; QString id; QString title; QString description; QDateTime date; PhotoId photoId = 0; - std::vector extended; + std::vector extended; uint64 credits = 0; uint64 bareMsgId = 0; uint64 barePeerId = 0; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 5b1ab8374..4004c04bd 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -569,26 +569,20 @@ object_ptr HistoryEntryPhoto( photoSize); } -object_ptr HistoryEntryVideo( - not_null parent, - not_null video, - int photoSize) { - return GenericEntryPhoto( - parent, - [=](Fn update) { - return Ui::GenerateCreditsPaintEntryCallback(video, update); - }, - photoSize); -} - -object_ptr PaidMediaPhoto( +object_ptr PaidMediaThumbnail( not_null parent, not_null photo, + PhotoData *second, + int totalCount, int photoSize) { return GenericEntryPhoto( parent, [=](Fn update) { - return Ui::GeneratePaidMediaPaintCallback(photo, update); + return Ui::GeneratePaidMediaPaintCallback( + photo, + second, + totalCount, + update); }, photoSize); } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 2bc3f7f1c..efea501b9 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -65,14 +65,11 @@ void ReceiptCreditsBox( not_null photo, int photoSize); -[[nodiscard]] object_ptr HistoryEntryVideo( - not_null parent, - not_null video, - int photoSize); - -[[nodiscard]] object_ptr PaidMediaPhoto( +[[nodiscard]] object_ptr PaidMediaThumbnail( not_null parent, not_null photo, + PhotoData *second, + int totalCount, int photoSize); void SmallBalanceBox( diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index c662619b6..6cd783f89 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -37,21 +37,62 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Ui { +namespace { -using PaintRoundImageCallback = Fn; +PaintRoundImageCallback MultiThumbnail( + PaintRoundImageCallback first, + PaintRoundImageCallback second, + int totalCount) { + const auto cache = std::make_shared(); + return [=](Painter &p, int x, int y, int outerWidth, int size) { + const auto stroke = st::lineWidth * 2; + const auto shift = stroke * 3; + if (size <= 2 * shift) { + first(p, x, y, outerWidth, size); + return; + } + const auto smaller = size - shift; + const auto ratio = style::DevicePixelRatio(); + const auto full = QSize(size, size) * ratio; + if (cache->size() != full) { + *cache = QImage(full, QImage::Format_ARGB32_Premultiplied); + cache->setDevicePixelRatio(ratio); + } + cache->fill(Qt::transparent); + auto q = Painter(cache.get()); + second(q, shift, 0, outerWidth, smaller); + q.setCompositionMode(QPainter::CompositionMode_Source); + q.setPen(QPen(Qt::transparent, 2 * stroke)); + q.setBrush(Qt::NoBrush); + const auto radius = st::roundRadiusLarge; + auto hq = PainterHighQualityEnabler(q); + q.drawRoundedRect(QRect(0, shift, smaller, smaller), radius, radius); + q.setCompositionMode(QPainter::CompositionMode_SourceOver); + first(q, 0, shift, outerWidth, smaller); + q.setPen(Qt::NoPen); + q.setBrush(st::shadowFg); + q.drawRoundedRect(QRect(0, shift, smaller, smaller), radius, radius); + q.setPen(st::toastFg); + q.setFont(style::font(smaller / 2, style::FontFlag::Semibold, 0)); + q.drawText( + QRect(0, shift, smaller, smaller), + QString::number(totalCount), + style::al_center); + q.end(); + + p.drawImage(x, y, *cache); + }; +} + +} // namespace QImage GenerateStars(int height, int count) { constexpr auto kOutlineWidth = .6; constexpr auto kStrokeWidth = 3; constexpr auto kShift = 3; - auto colorized = qs(Ui::Premium::ColorizedSvg( - Ui::Premium::CreditsIconGradientStops())); + auto colorized = qs(Premium::ColorizedSvg( + Premium::CreditsIconGradientStops())); colorized.replace( u"stroke=\"none\""_q, u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name())); @@ -98,10 +139,10 @@ QImage GenerateStars(int height, int count) { return frame; } -not_null CreateSingleStarWidget( - not_null parent, +not_null CreateSingleStarWidget( + not_null parent, int height) { - const auto widget = Ui::CreateChild(parent); + const auto widget = CreateChild(parent); const auto image = GenerateStars(height, 1); widget->resize(image.size() / style::DevicePixelRatio()); widget->paintRequest( @@ -113,15 +154,15 @@ not_null CreateSingleStarWidget( return widget; } -not_null AddInputFieldForCredits( - not_null container, +not_null AddInputFieldForCredits( + not_null container, rpl::producer value) { const auto &st = st::botEarnInputField; const auto inputContainer = container->add( CreateSkipWidget(container, st.heightMin)); const auto currentValue = rpl::variable( rpl::duplicate(value)); - const auto input = Ui::CreateChild( + const auto input = CreateChild( inputContainer, st, tr::lng_bot_earn_out_ph(), @@ -146,16 +187,16 @@ not_null AddInputFieldForCredits( st::boxRowPadding.left(), st.textMargins.top()); }, input->lifetime()); - Ui::ToggleChildrenVisibility(inputContainer, true); + ToggleChildrenVisibility(inputContainer, true); return input; } PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry) { - const auto bg = [&]() -> Ui::EmptyUserpic::BgColors { + const auto bg = [&]() -> EmptyUserpic::BgColors { switch (entry.peerType) { case Data::CreditsHistoryEntry::PeerType::Peer: - return Ui::EmptyUserpic::UserpicColor(0); + return EmptyUserpic::UserpicColor(0); case Data::CreditsHistoryEntry::PeerType::AppStore: return { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 }; case Data::CreditsHistoryEntry::PeerType::PlayMarket: @@ -172,7 +213,7 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( } Unexpected("Unknown peer type."); }(); - const auto userpic = std::make_shared(bg, QString()); + const auto userpic = std::make_shared(bg, QString()); return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { userpic->paintCircle(p, x, y, outerWidth, size); using PeerType = Data::CreditsHistoryEntry::PeerType; @@ -190,7 +231,7 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( }; } -Fn GenerateCreditsPaintEntryCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( not_null photo, Fn update) { struct State { @@ -240,7 +281,7 @@ Fn GenerateCreditsPaintEntryCallback( }; } -Fn GenerateCreditsPaintEntryCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( not_null video, Fn update) { struct State { @@ -286,7 +327,36 @@ Fn GenerateCreditsPaintEntryCallback( }; } -Fn GeneratePaidMediaPaintCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( + not_null session, + Data::CreditsHistoryMedia media, + Fn update) { + return (media.type == Data::CreditsHistoryMediaType::Photo) + ? GenerateCreditsPaintEntryCallback( + session->data().photo(media.id), + std::move(update)) + : GenerateCreditsPaintEntryCallback( + session->data().document(media.id), + std::move(update)); +} + +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( + not_null session, + const std::vector &media, + Fn update) { + if (media.size() == 1) { + return GenerateCreditsPaintEntryCallback( + session, + media.front(), + std::move(update)); + } + return MultiThumbnail( + GenerateCreditsPaintEntryCallback(session, media[0], update), + GenerateCreditsPaintEntryCallback(session, media[1], update), + media.size()); +} + +PaintRoundImageCallback GeneratePaidPhotoPaintCallback( not_null photo, Fn update) { struct State { @@ -294,7 +364,8 @@ Fn GeneratePaidMediaPaintCallback( } QImage image; - Ui::SpoilerAnimation spoiler; + QImage spoilerCornerCache; + SpoilerAnimation spoiler; }; const auto state = std::make_shared(update); @@ -322,36 +393,49 @@ Fn GeneratePaidMediaPaintCallback( { .options = Images::Option::RoundLarge }); } p.drawImage(x, y, state->image); - Ui::FillSpoilerRect( + FillSpoilerRect( p, QRect(x, y, size, size), - Ui::DefaultImageSpoiler().frame( - state->spoiler.index(crl::now(), false))); + Images::CornersMaskRef( + Images::CornersMask(ImageRoundRadius::Large)), + DefaultImageSpoiler().frame( + state->spoiler.index(crl::now(), false)), + state->spoilerCornerCache); }; } -Fn(Fn)> PaintPreviewCallback( +PaintRoundImageCallback GeneratePaidMediaPaintCallback( + not_null photo, + PhotoData *second, + int totalCount, + Fn update) { + if (!second) { + return GeneratePaidPhotoPaintCallback(photo, update); + } + return MultiThumbnail( + GeneratePaidPhotoPaintCallback(photo, update), + GeneratePaidPhotoPaintCallback(second, update), + totalCount); +} + +Fn)> PaintPreviewCallback( not_null session, const Data::CreditsHistoryEntry &entry) { - using MediaType = Data::CreditsHistoryEntry::MediaType; + using MediaType = Data::CreditsHistoryMediaType; const auto &extended = entry.extended; - const auto owner = &session->data(); - const auto photo = entry.photoId - ? owner->photo(entry.photoId).get() - : (!extended.empty() && extended.front().type == MediaType::Photo) - ? owner->photo(extended.front().mediaId).get() - : nullptr; - const auto video = (!extended.empty() - && extended.front().type == MediaType::Video) - ? owner->document(extended.front().mediaId).get() - : nullptr; - if (photo) { + if (!extended.empty()) { return [=](Fn update) { - return Ui::GenerateCreditsPaintEntryCallback(photo, update); + return GenerateCreditsPaintEntryCallback( + session, + extended, + std::move(update)); }; - } else if (video) { + } else if (entry.photoId) { + const auto photo = session->data().photo(entry.photoId); return [=](Fn update) { - return Ui::GenerateCreditsPaintEntryCallback(video, update); + return GenerateCreditsPaintEntryCallback( + photo, + std::move(update)); }; } return nullptr; diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index 4585fe7c0..70d983056 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -12,6 +12,7 @@ class DocumentData; namespace Data { struct CreditsHistoryEntry; +struct CreditsHistoryMedia; } // namespace Data namespace Main { @@ -26,6 +27,13 @@ class VerticalLayout; namespace Ui { +using PaintRoundImageCallback = Fn; + [[nodiscard]] QImage GenerateStars(int height, int count); [[nodiscard]] not_null CreateSingleStarWidget( @@ -36,22 +44,29 @@ namespace Ui { not_null container, rpl::producer value); -Fn GenerateCreditsPaintUserpicCallback( +PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry); -Fn GenerateCreditsPaintEntryCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( not_null photo, Fn update); -Fn GenerateCreditsPaintEntryCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( not_null video, Fn update); -Fn GeneratePaidMediaPaintCallback( - not_null photo, +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( + not_null session, + Data::CreditsHistoryMedia media, Fn update); -Fn(Fn)> PaintPreviewCallback( +PaintRoundImageCallback GeneratePaidMediaPaintCallback( + not_null photo, + PhotoData *second, + int totalCount, + Fn update); + +Fn)> PaintPreviewCallback( not_null session, const Data::CreditsHistoryEntry &entry);