Start emoji interactions playback.

This commit is contained in:
John Preston 2021-09-15 17:49:06 +03:00
parent b6fafdd8f7
commit 15f83892a1
9 changed files with 263 additions and 14 deletions

View File

@ -100,7 +100,7 @@ void EmojiInteractions::start(not_null<const HistoryView::Element*> 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<HistoryView::Sticker>(
// 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;

View File

@ -26,12 +26,22 @@ class Element;
namespace ChatHelpers {
struct EmojiInteractionPlayRequest {
not_null<HistoryItem*> item;
std::shared_ptr<Data::DocumentMedia> media;
};
class EmojiInteractions final {
public:
explicit EmojiInteractions(not_null<Main::Session*> session);
~EmojiInteractions();
using PlayRequest = EmojiInteractionPlayRequest;
void start(not_null<const HistoryView::Element*> view);
[[nodiscard]] rpl::producer<PlayRequest> playRequests() const {
return _playRequests.events();
}
private:
struct Animation {
@ -63,6 +73,7 @@ private:
not_null<HistoryItem*>,
std::vector<Animation>> _animations;
base::Timer _checkTimer;
rpl::event_stream<PlayRequest> _playRequests;
bool _waitingForDownload = false;
rpl::lifetime _downloadCheckLifetime;

View File

@ -60,11 +60,12 @@ auto LottieFromDocument(
Method &&method,
not_null<Data::DocumentMedia*> 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<Lottie::SinglePlayer> 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<Lottie::Animation*> LottieAnimationFromDocument(
@ -126,7 +130,8 @@ not_null<Lottie::Animation*> LottieAnimationFromDocument(
const auto method = [&](auto &&...args) {
return player->append(std::forward<decltype(args)>(args)...);
};
return LottieFromDocument(method, media, uint8(sizeTag), box);
const auto limit = kDontCacheLottieAfterArea;
return LottieFromDocument(method, media, uint8(sizeTag), box, limit);
}
bool HasLottieThumbnail(

View File

@ -44,6 +44,7 @@ enum class StickerLottieSize : uchar {
StickersFooter,
SetsListThumbnail,
InlineResults,
EmojiInteraction,
};
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(

View File

@ -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<HistoryView::EmojiInteractions>(
&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) {

View File

@ -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<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
const not_null<History*> _history;
const std::unique_ptr<HistoryView::EmojiInteractions> _emojiInteractions;
std::shared_ptr<Ui::ChatTheme> _theme;
History *_migrated = nullptr;

View File

@ -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<Main::Session*> session)
: _session(session) {
_session->data().viewRemoved(
) | rpl::filter([=] {
return !_plays.empty();
}) | rpl::start_with_next([=](not_null<const Element*> 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<Element*> 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<Element*> 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<QRect> EmojiInteractions::updateRequests() const {
return _updateRequests.events();
}
} // namespace HistoryView

View File

@ -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<Main::Session*> session);
~EmojiInteractions();
void play(
ChatHelpers::EmojiInteractionPlayRequest request,
not_null<Element*> view);
void visibleAreaUpdated(int visibleTop, int visibleBottom);
void paint(QPainter &p);
[[nodiscard]] rpl::producer<QRect> updateRequests() const;
private:
struct Play {
not_null<Element*> view;
std::unique_ptr<Lottie::SinglePlayer> lottie;
QPoint shift;
bool finished = false;
};
[[nodiscard]] QRect computeRect(not_null<Element*> view) const;
const not_null<Main::Session*> _session;
int _visibleTop = 0;
int _visibleBottom = 0;
QSize _emojiSize;
std::vector<Play> _plays;
rpl::event_stream<QRect> _updateRequests;
rpl::lifetime _lifetime;
};
} // namespace HistoryView

@ -1 +1 @@
Subproject commit 25294ef8da339f23e87d3cf0dbe8f29491712cfc
Subproject commit a8ffe6cb920ca400de03127cbf5bc5b5ed32aff4