From 15f83892a11f579dc55c1d87473b3e0c6da67717 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 15 Sep 2021 17:49:06 +0300 Subject: [PATCH] Start emoji interactions playback. --- .../chat_helpers/emoji_interactions.cpp | 12 +- .../chat_helpers/emoji_interactions.h | 11 ++ .../chat_helpers/stickers_lottie.cpp | 13 +- .../chat_helpers/stickers_lottie.h | 1 + .../history/history_inner_widget.cpp | 32 +++- .../history/history_inner_widget.h | 2 + .../view/history_view_emoji_interactions.cpp | 142 ++++++++++++++++++ .../view/history_view_emoji_interactions.h | 62 ++++++++ Telegram/lib_lottie | 2 +- 9 files changed, 263 insertions(+), 14 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_emoji_interactions.h diff --git a/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp b/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp index 4d20b79ea..f182772f7 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp @@ -100,7 +100,7 @@ void EmojiInteractions::start(not_null view) { auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult { auto nearest = kTimeNever; auto waitingForDownload = false; - for (auto &[id, animations] : _animations) { + for (auto &[item, animations] : _animations) { auto lastStartedAt = crl::time(); for (auto &animation : animations) { if (animation.startedAt) { @@ -111,11 +111,7 @@ auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult { break; } else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) { animation.startedAt = now; - - // #TODO interactions - //const auto sticker = std::make_unique( - // view, - // document); + _playRequests.fire({ item, animation.media }); break; } else { nearest = std::min(nearest, lastStartedAt + kMinDelay); @@ -174,8 +170,8 @@ void EmojiInteractions::sendAccumulated( auto EmojiInteractions::checkAccumulated(crl::time now) -> CheckResult { auto nearest = kTimeNever; for (auto i = begin(_animations); i != end(_animations);) { - auto &[id, animations] = *i; - sendAccumulated(now, id, animations); + auto &[item, animations] = *i; + sendAccumulated(now, item, animations); if (animations.empty()) { i = _animations.erase(i); continue; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_interactions.h b/Telegram/SourceFiles/chat_helpers/emoji_interactions.h index 2e06eff41..830fb7dbc 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_interactions.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_interactions.h @@ -26,12 +26,22 @@ class Element; namespace ChatHelpers { +struct EmojiInteractionPlayRequest { + not_null item; + std::shared_ptr media; +}; + class EmojiInteractions final { public: explicit EmojiInteractions(not_null session); ~EmojiInteractions(); + using PlayRequest = EmojiInteractionPlayRequest; + void start(not_null view); + [[nodiscard]] rpl::producer playRequests() const { + return _playRequests.events(); + } private: struct Animation { @@ -63,6 +73,7 @@ private: not_null, std::vector> _animations; base::Timer _checkTimer; + rpl::event_stream _playRequests; bool _waitingForDownload = false; rpl::lifetime _downloadCheckLifetime; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp index c10c5e95c..ae3ccd078 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp @@ -60,11 +60,12 @@ auto LottieFromDocument( Method &&method, not_null media, uint8 keyShift, - QSize box) { + QSize box, + int cacheAreaLimit) { const auto document = media->owner(); const auto data = media->bytes(); const auto filepath = document->filepath(); - if (box.width() * box.height() > kDontCacheLottieAfterArea) { + if (box.width() * box.height() > cacheAreaLimit) { // Don't use frame caching for large stickers. return method( Lottie::ReadContent(data, filepath), @@ -113,9 +114,12 @@ std::unique_ptr LottiePlayerFromDocument( replacements, std::move(renderer)); }; + const auto limit = (sizeTag == StickerLottieSize::EmojiInteraction) + ? (3 * kDontCacheLottieAfterArea) + : kDontCacheLottieAfterArea; const auto tag = replacements ? replacements->tag : uint8(0); const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F); - return LottieFromDocument(method, media, uint8(keyShift), box); + return LottieFromDocument(method, media, uint8(keyShift), box, limit); } not_null LottieAnimationFromDocument( @@ -126,7 +130,8 @@ not_null LottieAnimationFromDocument( const auto method = [&](auto &&...args) { return player->append(std::forward(args)...); }; - return LottieFromDocument(method, media, uint8(sizeTag), box); + const auto limit = kDontCacheLottieAfterArea; + return LottieFromDocument(method, media, uint8(sizeTag), box, limit); } bool HasLottieThumbnail( diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.h b/Telegram/SourceFiles/chat_helpers/stickers_lottie.h index 546d4e4a1..d4274b665 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.h @@ -44,6 +44,7 @@ enum class StickerLottieSize : uchar { StickersFooter, SetsListThumbnail, InlineResults, + EmojiInteraction, }; [[nodiscard]] std::unique_ptr LottiePlayerFromDocument( diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 17d3b5af5..e5ef4e1f4 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_service_message.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_context_menu.h" +#include "history/view/history_view_emoji_interactions.h" #include "ui/chat/chat_theme.h" #include "ui/chat/chat_style.h" #include "ui/widgets/popup_menu.h" @@ -160,6 +161,8 @@ HistoryInner::HistoryInner( , _controller(controller) , _peer(history->peer) , _history(history) +, _emojiInteractions(std::make_unique( + &controller->session())) , _migrated(history->migrateFrom()) , _pathGradient( HistoryView::MakePathShiftGradient( @@ -195,6 +198,21 @@ HistoryInner::HistoryInner( update(); } }, lifetime()); + + using PlayRequest = ChatHelpers::EmojiInteractionPlayRequest; + _controller->emojiInteractions().playRequests( + ) | rpl::filter([=](const PlayRequest &request) { + return (request.item->history() == _history); + }) | rpl::start_with_next([=](const PlayRequest &request) { + if (const auto view = request.item->mainView()) { + _emojiInteractions->play(request, view); + } + }, lifetime()); + _emojiInteractions->updateRequests( + ) | rpl::start_with_next([=](QRect rect) { + update(rect.translated(0, _historyPaddingTop)); + }, lifetime()); + session().data().itemRemoved( ) | rpl::start_with_next( [this](auto item) { itemRemoved(item); }, @@ -834,6 +852,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } return true; }); + p.setOpacity(1.); + p.translate(0, _historyPaddingTop); + _emojiInteractions->paint(p); } } } @@ -2357,6 +2378,10 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) { const auto till = _visibleAreaBottom + pages * visibleAreaHeight; session().data().unloadHeavyViewParts(ElementDelegate(), from, till); checkHistoryActivation(); + + _emojiInteractions->visibleAreaUpdated( + _visibleAreaTop - _historyPaddingTop, + _visibleAreaBottom - _historyPaddingTop); } bool HistoryInner::displayScrollDate() const { @@ -2454,7 +2479,12 @@ void HistoryInner::updateSize() { _botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom()); } - _historyPaddingTop = newHistoryPaddingTop; + if (_historyPaddingTop != newHistoryPaddingTop) { + _historyPaddingTop = newHistoryPaddingTop; + _emojiInteractions->visibleAreaUpdated( + _visibleAreaTop - _historyPaddingTop, + _visibleAreaBottom - _historyPaddingTop); + } int newHeight = _historyPaddingTop + itemsHeight + st::historyPaddingBottom; if (width() != _scroll->width() || height() != newHeight) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 626e2f657..aa29b507b 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -21,6 +21,7 @@ class CloudImageView; namespace HistoryView { class ElementDelegate; +class EmojiInteractions; struct TextState; struct StateRequest; enum class CursorState : char; @@ -354,6 +355,7 @@ private: const not_null _controller; const not_null _peer; const not_null _history; + const std::unique_ptr _emojiInteractions; std::shared_ptr _theme; History *_migrated = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp new file mode 100644 index 000000000..a321eaaa3 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -0,0 +1,142 @@ +/* +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 "history/view/history_view_emoji_interactions.h" + +#include "history/view/history_view_element.h" +#include "history/view/media/history_view_sticker.h" +#include "history/history.h" +#include "chat_helpers/emoji_interactions.h" +#include "chat_helpers/stickers_lottie.h" +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "lottie/lottie_common.h" +#include "lottie/lottie_single_player.h" +#include "base/random.h" +#include "styles/style_chat.h" + +namespace HistoryView { +namespace { + +constexpr auto kSizeMultiplier = 3; + +[[nodiscard]] QPoint GenerateRandomShift(QSize emoji) { + // Random shift in [-0.08 ... 0.08] of animated emoji size. + const auto maxShift = emoji * 2 / 25; + return { + base::RandomIndex(maxShift.width() * 2 + 1) - maxShift.width(), + base::RandomIndex(maxShift.height() * 2 + 1) - maxShift.height(), + }; +} + +} // namespace + +EmojiInteractions::EmojiInteractions(not_null session) +: _session(session) { + _session->data().viewRemoved( + ) | rpl::filter([=] { + return !_plays.empty(); + }) | rpl::start_with_next([=](not_null view) { + _plays.erase(ranges::remove(_plays, view, &Play::view), end(_plays)); + }, _lifetime); + + _emojiSize = Sticker::EmojiSize(); +} + +EmojiInteractions::~EmojiInteractions() = default; + +void EmojiInteractions::play( + ChatHelpers::EmojiInteractionPlayRequest request, + not_null view) { + const auto top = view->block()->y() + view->y(); + const auto bottom = top + view->height(); + if (_visibleTop >= bottom + || _visibleBottom <= top + || _visibleTop == _visibleBottom) { + return; + } + auto lottie = ChatHelpers::LottiePlayerFromDocument( + request.media.get(), + nullptr, + ChatHelpers::StickerLottieSize::EmojiInteraction, + _emojiSize * kSizeMultiplier * style::DevicePixelRatio(), + Lottie::Quality::High); + const auto shift = GenerateRandomShift(_emojiSize); + lottie->updates( + ) | rpl::start_with_next([=](Lottie::Update update) { + v::match(update.data, [&](const Lottie::Information &information) { + }, [&](const Lottie::DisplayFrameRequest &request) { + const auto rect = computeRect(view).translated(shift); + if (rect.y() + rect.height() >= _visibleTop + && rect.y() <= _visibleBottom) { + _updateRequests.fire_copy(rect); + } + }); + }, lottie->lifetime()); + _plays.push_back({ + .view = view, + .lottie = std::move(lottie), + .shift = shift, + }); +} + +void EmojiInteractions::visibleAreaUpdated( + int visibleTop, + int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; +} + +QRect EmojiInteractions::computeRect(not_null view) const { + const auto fullWidth = view->width(); + const auto shift = (_emojiSize.width() * kSizeMultiplier) / 40; + const auto skip = (view->hasFromPhoto() ? st::msgPhotoSkip : 0) + + st::msgMargin.left(); + const auto rightAligned = view->hasOutLayout() + && !view->delegate()->elementIsChatWide(); + const auto left = rightAligned + ? (fullWidth - skip + shift - _emojiSize.width() * kSizeMultiplier) + : (skip - shift); + const auto viewTop = view->block()->y() + view->y() + view->marginTop(); + const auto top = viewTop - _emojiSize.height(); + return QRect(QPoint(left, top), _emojiSize * kSizeMultiplier); +} + +void EmojiInteractions::paint(QPainter &p) { + const auto factor = style::DevicePixelRatio(); + for (auto &play : _plays) { + if (!play.lottie->ready()) { + continue; + } + auto request = Lottie::FrameRequest(); + request.box = _emojiSize * kSizeMultiplier * factor; + const auto rightAligned = play.view->hasOutLayout() + && !play.view->delegate()->elementIsChatWide(); + if (!rightAligned) { + // #TODO interactions mirror + request.colored = st::msgStickerOverlay->c; + } + const auto frame = play.lottie->frameInfo(request); + if (frame.index + 1 == play.lottie->information().framesCount) { + play.finished = true; + } + const auto rect = computeRect(play.view); + p.drawImage( + QRect(rect.topLeft() + play.shift, frame.image.size() / factor), + frame.image); + play.lottie->markFrameShown(); + } + _plays.erase(ranges::remove(_plays, true, &Play::finished), end(_plays)); +} + +rpl::producer EmojiInteractions::updateRequests() const { + return _updateRequests.events(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h new file mode 100644 index 000000000..dbbcfb5ec --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h @@ -0,0 +1,62 @@ +/* +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 +*/ +#pragma once + +namespace ChatHelpers { +struct EmojiInteractionPlayRequest; +} // namespace ChatHelpers + +namespace Lottie { +class SinglePlayer; +} // namespace Lottie + +namespace Main { +class Session; +} // namespace Main + +namespace HistoryView { + +class Element; + +class EmojiInteractions final { +public: + explicit EmojiInteractions(not_null session); + ~EmojiInteractions(); + + void play( + ChatHelpers::EmojiInteractionPlayRequest request, + not_null view); + void visibleAreaUpdated(int visibleTop, int visibleBottom); + + void paint(QPainter &p); + [[nodiscard]] rpl::producer updateRequests() const; + +private: + struct Play { + not_null view; + std::unique_ptr lottie; + QPoint shift; + bool finished = false; + }; + + [[nodiscard]] QRect computeRect(not_null view) const; + + const not_null _session; + + int _visibleTop = 0; + int _visibleBottom = 0; + QSize _emojiSize; + + std::vector _plays; + rpl::event_stream _updateRequests; + + rpl::lifetime _lifetime; + +}; + +} // namespace HistoryView diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index 25294ef8d..a8ffe6cb9 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit 25294ef8da339f23e87d3cf0dbe8f29491712cfc +Subproject commit a8ffe6cb920ca400de03127cbf5bc5b5ed32aff4