diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 30f3127451..76a2a18b76 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -286,7 +286,7 @@ void PreloadSticker(const std::shared_ptr &media) { document, media->videoThumbnailContent(), QString(), - true); + Stickers::EffectType::PremiumSticker); const auto update = [=] { if (!state->readyInvoked diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index f4cc13c4c1..af07736a48 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -269,10 +269,10 @@ std::unique_ptr EmojiPack::effectPlayer( not_null document, QByteArray data, QString filepath, - bool premium) { + EffectType type) { // Shortened copy from stickers_lottie module. const auto baseKey = document->bigFileBaseCacheKey(); - const auto tag = uint8(0); + const auto tag = uint8(type); const auto keyShift = ((tag << 4) & 0xF0) | (uint8(ChatHelpers::StickerLottieSize::EmojiInteraction) & 0x0F); const auto key = Storage::Cache::Key{ @@ -292,19 +292,24 @@ std::unique_ptr EmojiPack::effectPlayer( std::move(data)); }); }; - const auto size = premium + const auto size = (type == EffectType::PremiumSticker) ? HistoryView::Sticker::PremiumEffectSize(document) - : HistoryView::Sticker::EmojiEffectSize(); + : (type == EffectType::EmojiInteraction) + ? HistoryView::Sticker::EmojiEffectSize() + : HistoryView::Sticker::MessageEffectSize(); const auto request = Lottie::FrameRequest{ size * style::DevicePixelRatio(), }; - auto &weakProvider = _sharedProviders[document]; + auto &weakProvider = _sharedProviders[{ document, type }]; auto shared = [&] { if (const auto result = weakProvider.lock()) { return result; } + const auto count = (type == EffectType::PremiumSticker) + ? kPremiumCachesCount + : kEmojiCachesCount; const auto result = Lottie::SinglePlayer::SharedProvider( - premium ? kPremiumCachesCount : kEmojiCachesCount, + count, get, put, Lottie::ReadContent(data, filepath), diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h index 0226b04ef6..133880e1a5 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h @@ -50,6 +50,12 @@ struct LargeEmojiImage { [[nodiscard]] static QSize Size(); }; +enum class EffectType : uint8 { + EmojiInteraction, + PremiumSticker, + MessageEffect, +}; + class EmojiPack final { public: using ViewElement = HistoryView::Element; @@ -95,11 +101,23 @@ public: not_null document, QByteArray data, QString filepath, - bool premium); + EffectType type); private: class ImageLoader; + struct ProviderKey { + not_null document; + Stickers::EffectType type = {}; + + friend inline auto operator<=>( + const ProviderKey &, + const ProviderKey &) = default; + friend inline bool operator==( + const ProviderKey &, + const ProviderKey &) = default; + }; + void refresh(); void refreshDelayed(); void refreshAnimations(); @@ -135,7 +153,7 @@ private: mtpRequestId _animationsRequestId = 0; base::flat_map< - not_null, + ProviderKey, std::weak_ptr> _sharedProviders; rpl::event_stream<> _refreshed; diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 8682ba6553..a04cbcbfb5 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -737,6 +737,9 @@ void Reactions::resolveEffectImages() { LOG(("API Error: Effect '%1' not found!" ).arg(ReactionIdToLog(id))); } + if (i != end(_effects)) { + preloadEffect(*i); + } } } diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index c7461f3fc9..8af94c73bc 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -173,20 +173,10 @@ ClickHandlerPtr BottomInfo::replayEffectLink( left += width() - available; top += st::msgDateFont->height; } - auto x = left; - auto y = top; - auto widthLeft = available; if (_effect) { - const auto add = st::reactionInfoBetween; - const auto width = st::reactionInfoSize; - if (x > left && widthLeft < width) { - x = left; - y += st::msgDateFont->height; - widthLeft = available; - } const auto image = QRect( - x, - y, + left, + top, st::reactionInfoSize, st::msgDateFont->height); if (image.contains(position)) { @@ -195,8 +185,6 @@ ClickHandlerPtr BottomInfo::replayEffectLink( } return _replayLink; } - x += width + add; - widthLeft -= width + add; } return nullptr; } @@ -544,6 +532,25 @@ void BottomInfo::continueEffectAnimation( } } +QRect BottomInfo::effectIconGeometry() const { + if (!_effect) { + return {}; + } + auto left = 0; + auto top = 0; + auto available = width(); + if (height() != minHeight()) { + available = std::min(available, _effectMaxWidth); + left += width() - available; + top += st::msgDateFont->height; + } + return QRect( + left + (st::reactionInfoSize - st::effectInfoImage) / 2, + top + (st::msgDateFont->height - st::effectInfoImage) / 2, + st::effectInfoImage, + st::effectInfoImage); +} + BottomInfo::Data BottomInfoDataFromMessage(not_null message) { using Flag = BottomInfo::Data::Flag; const auto item = message->data(); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index c585281be0..594a488f13 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -82,6 +82,8 @@ public: void continueEffectAnimation( std::unique_ptr animation); + QRect effectIconGeometry() const; + private: struct Effect; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index ef5b0c830c..82688196a0 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1786,6 +1786,10 @@ auto Element::takeEffectAnimation() return nullptr; } +QRect Element::effectIconGeometry() const { + return QRect(); +} + Element::~Element() { // Delete media while owner still exists. clearSpecialOnlyEmoji(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 324640b6d6..ab458b1a5d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -523,6 +523,7 @@ public: void previousInBlocksChanged(); void nextInBlocksRemoved(); + [[nodiscard]] virtual QRect effectIconGeometry() const; [[nodiscard]] virtual QRect innerGeometry() const = 0; void customEmojiRepaint(); diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index c9b299a726..efda8ead15 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -119,7 +119,7 @@ bool EmojiInteractions::playPremiumEffect( document->createMediaView()->videoThumbnailContent(), QString(), false, - true); + Stickers::EffectType::PremiumSticker); } } } @@ -147,7 +147,7 @@ void EmojiInteractions::play( media->bytes(), media->owner()->filepath(), incoming, - false); + Stickers::EffectType::EmojiInteraction); } void EmojiInteractions::playEffectOnRead(not_null view) { @@ -214,7 +214,7 @@ void EmojiInteractions::playEffect( resolved.content, resolved.filepath, false, - false); + Stickers::EffectType::MessageEffect); } void EmojiInteractions::addPendingEffect(not_null view) { @@ -272,7 +272,7 @@ void EmojiInteractions::play( QByteArray data, QString filepath, bool incoming, - bool premium) { + Stickers::EffectType type) { const auto top = _itemTop(view); const auto bottom = top + view->height(); if (_visibleTop >= bottom @@ -286,12 +286,14 @@ void EmojiInteractions::play( document, data, filepath, - premium); + type); - const auto inner = premium + const auto inner = (type == Stickers::EffectType::PremiumSticker) ? HistoryView::Sticker::Size(document) : HistoryView::Sticker::EmojiSize(); - const auto shift = premium ? QPoint() : GenerateRandomShift(inner); + const auto shift = (type == Stickers::EffectType::EmojiInteraction) + ? GenerateRandomShift(inner) + : QPoint(); const auto raw = lottie.get(); lottie->updates( ) | rpl::start_with_next([=](Lottie::Update update) { @@ -312,16 +314,18 @@ void EmojiInteractions::play( .lottie = std::move(lottie), .shift = shift, .inner = inner, - .outer = (premium + .outer = ((type == Stickers::EffectType::PremiumSticker) ? HistoryView::Sticker::PremiumEffectSize(document) - : HistoryView::Sticker::EmojiEffectSize()), - .premium = premium, + : (type == Stickers::EffectType::EmojiInteraction) + ? HistoryView::Sticker::EmojiEffectSize() + : HistoryView::Sticker::MessageEffectSize()), + .type = type, }); if (incoming) { _playStarted.fire(std::move(emoticon)); } if (const auto media = view->media()) { - if (!premium) { + if (type == Stickers::EffectType::EmojiInteraction) { media->stickerClearLoopPlayed(); } } @@ -336,9 +340,28 @@ void EmojiInteractions::visibleAreaUpdated( QRect EmojiInteractions::computeRect(const Play &play) const { const auto view = play.view; + const auto viewTop = _itemTop(view); + if (viewTop < 0) { + return QRect(); + } + if (play.type == Stickers::EffectType::MessageEffect) { + const auto icon = view->effectIconGeometry(); + if (icon.isEmpty()) { + return QRect(); + } + const auto size = play.outer; + const auto shift = view->hasRightLayout() + ? (-size.width() / 3) + : (size.width() / 3); + return QRect( + shift + icon.x() + (icon.width() - size.width()) / 2, + viewTop + icon.y() + (icon.height() - size.height()) / 2, + size.width(), + size.height()); + } const auto sticker = play.inner; const auto size = play.outer; - const auto shift = play.premium + const auto shift = (play.type == Stickers::EffectType::PremiumSticker) ? int(sticker.width() * kPremiumShift) : (size.width() / 40); const auto inner = view->innerGeometry(); @@ -346,11 +369,9 @@ QRect EmojiInteractions::computeRect(const Play &play) const { const auto left = rightAligned ? (inner.x() + inner.width() + shift - size.width()) : (inner.x() - shift); - const auto viewTop = _itemTop(view) + inner.y(); - if (viewTop < 0) { - return QRect(); - } - const auto top = viewTop + (sticker.height() - size.height()) / 2; + const auto top = viewTop + + inner.y() + + (sticker.height() - size.height()) / 2; return QRect(QPoint(left, top), size).translated(play.shift); } diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h index b7af259975..679b102a58 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h @@ -23,6 +23,10 @@ namespace Main { class Session; } // namespace Main +namespace Stickers { +enum class EffectType : uint8; +} // namespace Stickers + namespace HistoryView { class Element; @@ -60,7 +64,7 @@ private: int frame = 0; int framesCount = 0; int frameRate = 0; - bool premium = false; + Stickers::EffectType type = {}; bool started = false; bool finished = false; }; @@ -96,7 +100,7 @@ private: QByteArray data, QString filepath, bool incoming, - bool premium); + Stickers::EffectType type); void checkDelayed(); void addPendingEffect(not_null view); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 32cf1f5ade..cf19c8f9a3 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -707,6 +707,88 @@ auto Message::takeEffectAnimation() return _bottomInfo.takeEffectAnimation(); } +QRect Message::effectIconGeometry() const { + const auto item = data(); + const auto media = this->media(); + + auto g = countGeometry(); + if (g.width() < 1 || isHidden()) { + return {}; + } + const auto bubble = drawBubble(); + const auto reactionsInBubble = _reactions && embedReactionsInBubble(); + const auto mediaDisplayed = media && media->isDisplayed(); + const auto keyboard = item->inlineReplyKeyboard(); + auto keyboardHeight = 0; + if (keyboard) { + keyboardHeight = keyboard->naturalHeight(); + g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight); + } + + const auto fromBottomInfo = [&](QPoint bottomRight) { + const auto size = _bottomInfo.currentSize(); + return _bottomInfo.effectIconGeometry().translated( + bottomRight - QPoint(size.width(), size.height())); + }; + if (bubble) { + auto entry = logEntryOriginal(); + + // Entry page is always a bubble bottom. + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); + + auto inner = g; + if (_comments) { + inner.setHeight(inner.height() - st::historyCommentsButtonHeight); + } + auto trect = inner.marginsRemoved(st::msgPadding); + const auto reactionsTop = (reactionsInBubble && !_viewButton) + ? st::mediaInBubbleSkip + : 0; + const auto reactionsHeight = reactionsInBubble + ? (reactionsTop + _reactions->height()) + : 0; + if (_viewButton) { + const auto belowInfo = _viewButton->belowMessageInfo(); + const auto infoHeight = reactionsInBubble + ? (reactionsHeight + 2 * st::mediaInBubbleSkip) + : _bottomInfo.height(); + const auto heightMargins = QMargins(0, 0, 0, infoHeight); + if (belowInfo) { + inner -= heightMargins; + } + trect.setHeight(trect.height() - _viewButton->height()); + if (reactionsInBubble) { + trect.setHeight(trect.height() - st::mediaInBubbleSkip + st::msgPadding.bottom()); + } else if (mediaDisplayed) { + trect.setHeight(trect.height() - st::mediaInBubbleSkip); + } + } + if (mediaOnBottom) { + trect.setHeight(trect.height() + + st::msgPadding.bottom() + - viewButtonHeight()); + } + if (mediaOnTop) { + trect.setY(trect.y() - st::msgPadding.top()); + } + if (mediaDisplayed && mediaOnBottom && media->customInfoLayout()) { + auto mediaHeight = media->height(); + auto mediaLeft = trect.x() - st::msgPadding.left(); + auto mediaTop = (trect.y() + trect.height() - mediaHeight); + return fromBottomInfo(QPoint(mediaLeft, mediaTop) + media->resolveCustomInfoRightBottom()); + } else { + return fromBottomInfo({ + inner.left() + inner.width() - (st::msgPadding.right() - st::msgDateDelta.x()), + inner.top() + inner.height() - (st::msgPadding.bottom() - st::msgDateDelta.y()), + }); + } + } else if (mediaDisplayed) { + return fromBottomInfo(g.topLeft() + media->resolveCustomInfoRightBottom()); + } + return {}; +} + QSize Message::performCountOptimalSize() { const auto item = data(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 77c7c5fad1..a8c172c03d 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -162,6 +162,7 @@ public: auto takeEffectAnimation() -> std::unique_ptr override; + QRect effectIconGeometry() const override; QRect innerGeometry() const override; [[nodiscard]] BottomRippleMask bottomRippleMask(int buttonHeight) const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index c8527d9993..69e8bc0af8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -41,6 +41,7 @@ constexpr auto kMaxSizeFixed = 512; constexpr auto kMaxEmojiSizeFixed = 256; constexpr auto kPremiumMultiplier = (1 + 0.245 * 2); constexpr auto kEmojiMultiplier = 3; +constexpr auto kMessageEffectMultiplier = 2; [[nodiscard]] QImage CacheDiceImage( const QString &emoji, @@ -208,6 +209,10 @@ QSize Sticker::EmojiEffectSize() { return EmojiSize() * kEmojiMultiplier; } +QSize Sticker::MessageEffectSize() { + return EmojiSize() * kMessageEffectMultiplier; +} + QSize Sticker::EmojiSize() { const auto side = std::min(st::maxAnimatedEmojiSize, kMaxEmojiSizeFixed); return { side, side }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index c6bbbd0daf..91a69f7e2d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -91,6 +91,7 @@ public: not_null document); [[nodiscard]] static QSize UsualPremiumEffectSize(); [[nodiscard]] static QSize EmojiEffectSize(); + [[nodiscard]] static QSize MessageEffectSize(); [[nodiscard]] static QSize EmojiSize(); [[nodiscard]] static ClickHandlerPtr ShowSetHandler( not_null document); diff --git a/Telegram/SourceFiles/window/window_media_preview.cpp b/Telegram/SourceFiles/window/window_media_preview.cpp index 3aa79501de..87595711c9 100644 --- a/Telegram/SourceFiles/window/window_media_preview.cpp +++ b/Telegram/SourceFiles/window/window_media_preview.cpp @@ -339,7 +339,7 @@ void MediaPreviewWidget::setupLottie() { _document, _documentMedia->videoThumbnailContent(), QString(), - true); + Stickers::EffectType::PremiumSticker); } else { const auto size = currentDimensions(); _lottie = std::make_unique(